AWS CLIからAWS WAFのWeb ACLを定義してみた #reinvent

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

AWS CLI 1.8.10 がリリースされ、本日発表された AWS WAF にも対応したため、早速 AWS CLI から WAF の API を叩いて、リクエストヘッダーベースのアクセス制御を行いました。

先に使った感想を申しますと CLI からの操作は過去の経験からでは route53 なみに面倒です。

CLI を使って --generate-skeleton で入力パラメーターを確認し、--cli-input-json でパラメーターを食わせ、API ドキュメントやエラーメッセージと睨めっとしながらの作業で何度も挫けそうになりました。

WAF オブジェクトの関係性

WAF には大きく分けて

  • Web ACL
  • Rule
  • Condition
  • Filter

の4オブジェクトがあります。

Condition は

  • IP addresses
  • SQL injection
  • String matching

の中から種類を選べ、例えば String maching であれば

  • リクエストヘッダー
  • HTTP Method
  • クエリーストリング
  • URL の文字列

の中から Filter を複数設定出来ます。

この Condition を束ねたものが Rule です。 さらにこの Rule を束ねたものが Web ACL です

Web ACL は最終的に CloudFront のアクセスコントロールとして設定します。

waf-object-relation

この関係性を頭にいれながら API を叩きましょう。

作業の流れ

次の手順で操作します。

  1. AWS CLI のアップデート
  2. WEB ACL の作成
  3. Condition の作成
  4. Rule の作成
  5. CloudFront と紐付ける
  6. CloudFrontにアクセスをして確認

AWS WAF 更新系 API の注意点

AWS WAF オブジェクトに対する更新系(CREATE/UPDATE/DELETE) API では GetChangeToken API でトークンを取得し、リクエストにこのトークンを含めることで、更新のコンフリクトを防いでいます。

詳細は次の API マニュアルをご確認ください。

http://docs.aws.amazon.com/waf/latest/APIReference/API_GetChangeToken.html

AWS CLI のアップデート

何はともあれ AWS CLI を 1.8.10 以上にアップデートします。

$ sudo pip install awscli --upgrade
$ aws --version
aws-cli/1.8.10 Python/2.7.10 Linux/4.1.7-15.23.amzn1.x86_64

waf の help コマンドを叩いて、コマンド一覧を確認しましょう。

$ aws waf help

WAF()                                                                    WAF()



NAME
       waf -

DESCRIPTION
       This  is  the  AWS WAF API Reference . This guide is for developers who
       need detailed information about the AWS WAF API  actions,  data  types,
       and  errors.  For  detailed  information  about AWS WAF features and an
       overview of how to use the AWS WAF API, see the AWS WAF Developer Guide
       .

AVAILABLE COMMANDS
       o create-byte-match-set

       o create-ip-set

       o create-rule

       o create-sql-injection-match-set

       o create-web-acl

       o delete-byte-match-set

       o delete-ip-set

       o delete-rule

       o delete-sql-injection-match-set

       o delete-web-acl

       o get-byte-match-set

       o get-change-token

       o get-change-token-status

       o get-ip-set

       o get-rule

       o get-sampled-requests

       o get-sql-injection-match-set

       o get-web-acl

       o help

       o list-byte-match-sets

       o list-ip-sets

       o list-rules

       o list-sql-injection-match-sets

       o list-web-acls

       o update-byte-match-set

       o update-ip-set

       o update-rule

       o update-sql-injection-match-set

       o update-web-acl

WEB ACL の作成

create-web-acl API で WEB ACL を作成します。

$ aws waf get-change-token
{
    "ChangeToken": "b8782710-497d-49bf-ad38-5ff225bf9aaf"
}

$ cat create-web-acl.json
{
    "Name": "waftest",
    "MetricName": "waftest",
    "DefaultAction": {
        "Type": "ALLOW"
    },
    "ChangeToken" : "b8782710-497d-49bf-ad38-5ff225bf9aaf"
}

$ aws waf create-web-acl --cli-input-json file://create-web-acl.json
{
    "WebACL": {
        "DefaultAction": {
            "Type": "ALLOW"
        },
        "Rules": [],
        "WebACLId": "0026186b-410f-46d0-b701-eb87241cdafa",
        "Name": "waftest"
    },
    "ChangeToken": "b8782710-497d-49bf-ad38-5ff225bf9aaf"
}

Condition の作成

WAF で制御する条件を定義します。

今回は、リクエストヘッダーの User-Agent に iPhone が含まれるクライアントのみにアクセスを許可します。 String maching ベースの Condition を作成し、この Condition に対して 上記 Filter 条件を定義します。

String maching Condition の作成には create-byte-match-set API を利用します。

$ aws waf get-change-token
{
    "ChangeToken": "8f9ca9c1-a21c-b6e4-962f-ef0d3b9b6019"
}

$ cat byte-match-set.json
{
    "Name": "iphone-header",
    "ChangeToken": "8f9ca9c1-a21c-b6e4-962f-ef0d3b9b6019"
}
$ aws waf create-byte-match-set --cli-input-json  file://byte-match-set.json
{
    "ChangeToken": "8f9ca9c1-a21c-b6e4-962f-ef0d3b9b6019",
    "ByteMatchSet": {
        "ByteMatchSetId": "309a5b49-767d-41c0-8f4b-8fcd97ac48e5",
        "Name": "iphone-header",
        "ByteMatchTuples": []
    }
}

この Condition に対して Filter を追加します。

下の例の JSON ファイル update-byte-match-set.json において "ByteMatchTuple" で記述しているものが String matching ベースの Filter 条件です。

「リクエストヘッダーの User-Agent に "iPhone" が含まれる」という Filter を意味します。

先ほど作成した String matching Condition は "ByteMatchSetId" で指定します。

$ aws waf get-change-token
{
    "ChangeToken": "f1fe1e61-683d-4b66-b115-d4cd088c90c2"
}
$ cat update-byte-match-set.json
{
    "ByteMatchSetId": "309a5b49-767d-41c0-8f4b-8fcd97ac48e5",
    "ChangeToken": "f1fe1e61-683d-4b66-b115-d4cd088c90c2",
    "Updates": [
        {
            "Action": "INSERT",
            "ByteMatchTuple": {
                "FieldToMatch": {
                    "Data": "user-agent",
                    "Type": "HEADER"
                },
                "TargetString": "iPhone",
                "TextTransformation": "NONE",
                "PositionalConstraint": "CONTAINS"
            }
        }
    ]
}
$ aws waf update-byte-match-set --cli-input-json file://update-byte-match-set.json
{
    "ChangeToken": "f1fe1e61-683d-4b66-b115-d4cd088c90c2"
}

Rule の作成

今回は、リクエストヘッダーの User-Agent に iPhone が含まれるクライアントのみにアクセスを許可します。 Rule を作成し、この Rule に対して 上記 Condition を設定します。

Rule の作成には create-rule API を利用します。

$ aws waf get-change-token
{
    "ChangeToken": "c6d1d95c-fc6e-3de6-9409-d07f98c2a631"
}

$ cat create-rule.json
{
    "Name": "waftest",
    "MetricName": "waftest",
    "ChangeToken": "c6d1d95c-fc6e-3de6-9409-d07f98c2a631"
}
$ aws waf create-rule --cli-input-json file://create-rule.json
{
    "ChangeToken": "c6d1d95c-fc6e-3de6-9409-d07f98c2a631",
    "Rule": {
        "Predicates": [],
        "MetricName": "waftest",
        "Name": "waftest",
        "RuleId": "ec3bda6e-de6b-44a6-a835-03718aa897ad"
    }
}

この Rule に対して update-rule API で Condition を追加します。

下の例の JSON ファイル update-rule.json において "Updates" で記述しているものが Condition です。

先ほど作成した String matching Condition は "DataId" で指定します。 "ByteMatchSetId" ではないためお間違えの内容にお願いします。

$ aws waf get-change-token
{
    "ChangeToken": "0914f17c-bd7a-4d01-b302-b4e37b4c7fb0"
}
$ cat update-rule.json
{
    "RuleId": "ec3bda6e-de6b-44a6-a835-03718aa897ad",
    "ChangeToken": "0914f17c-bd7a-4d01-b302-b4e37b4c7fb0",
    "Updates": [
        {
            "Action": "INSERT",
            "Predicate": {
                "Negated": true,
                "Type": "ByteMatch",
                "DataId": "309a5b49-767d-41c0-8f4b-8fcd97ac48e5"
            }
        }
    ]
}
$ aws waf update-rule --cli-input-json file://update-rule.json
{
    "ChangeToken": "0914f17c-bd7a-4d01-b302-b4e37b4c7fb0"
}

この Rule を update-web-acl API で作成済み Web ACL に紐付けると WAF に閉じた設定は完了です。

下の JSON ファイル update-web-acl.json において "Updates" で記述しているものが Rule です。 Rule を "Type" : "ALLOW" で追加し、"DefaultAction" では "Type": "BLOCK" とします。 こうすることで、 Rule を満たすアクセスは許可され、満たさないアクセスは許可されなくなります。

先ほど作成した Rule は "RuleId" で指定します。

$ aws waf get-change-token
{
    "ChangeToken": "43cd5ea5-ac95-246e-ae21-9e96c3536192"
}
$ cat update-web-acl.json
{
    "WebACLId": "0026186b-410f-46d0-b701-eb87241cdafa",
    "ChangeToken": "43cd5ea5-ac95-246e-ae21-9e96c3536192",
    "Updates": [
        {
            "Action": "INSERT",
            "ActivatedRule": {
                "Priority": 1,
                "RuleId" : "ec3bda6e-de6b-44a6-a835-03718aa897ad",
                "Action": {
                    "Type": "ALLOW"
                }
            }
        }
    ],
    "DefaultAction": {
        "Type": "BLOCK"
    }
}

$ aws waf update-web-acl --cli-input-json file://update-web-acl.json
{
    "ChangeToken": "43cd5ea5-ac95-246e-ae21-9e96c3536192"
}

更新した Web ACL を get-web-acl API で確認してみましょう。

$ aws waf get-web-acl --web-acl-id "0026186b-410f-46d0-b701-eb87241cdafa"
{
    "WebACL": {
        "DefaultAction": {
            "Type": "BLOCK"
        },
        "Rules": [
            {
                "Priority": 1,
                "Action": {
                    "Type": "ALLOW"
                },
                "RuleId": "ec3bda6e-de6b-44a6-a835-03718aa897ad"
            }
        ],
        "WebACLId": "0026186b-410f-46d0-b701-eb87241cdafa",
        "Name": "waftest"
    }
}

CloudFront と紐付ける

CloudFront の編集画面で、先ほど作成した AWS WAF Web ACL を設定します。

CloudFrontにWeb ACLを設定

アクセスをして確認

それでは cloudfront に GET リクエストを投げてみましょう。 まずはリクエストヘッダーをカスタマイズせずにアクセスします。

$ curl  DUMMY.cloudfront.net
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>

<H1>ERROR</H1>


<H2>The request could not be satisfied.</H2>


<hr noshade size="1px">

Request blocked.
<BR clear="all">

<hr noshade size="1px">


<PRE>
Generated by cloudfront (CloudFront)
Request ID: VeeVtOdUprDz2wfIx3gozmorZvcPnthm8dZj4cyvBLvSdxw3jHaT3A==
</PRE>


<ADDRESS>
</ADDRESS>

</BODY></HTML>

Web ACL Rule に従って "ERROR: The request could not be satisfied" などとエラーメッセージが表示されていますね。

次に User-Agent に iPhone の文字を含めてアクセスしてみましょう。

$ curl --header "user-agent: iPhone" DUMMY.cloudfront.net
<body>
aws waf test
</body>

CloudFront がエラーを返さなくなりました。

まとめ

長い道のりでしたが CLI でゴリゴリと WAF 設定出来ました。

カジュアルに使うのであれば、マネージメントコンソールから画面ポチポチするのが素直ですが、設定の再現性やまとまった処理をするのであれば CLI や SDK が良いでしょう。

関連記事

マネージメントコンソールから特定のUser-Agentだけ通信させる方法については次の記事を参照ください。

AWS WAFで特定のUser-Agentだけ通信できるサイトを構築 #reinvent | Developers.IO

AWS WAF の API 一覧については本日の次の記事を参照ください。

AWS WAFのAPI一覧 #reinvent | Developers.IO