ALBで指定のセキュリティポリシー以外の使用を検出するAWS Configのカスタムルールを作成してみた
ALBでTLS 1.0が含まれているSSLポリシーを使用していたら検出したい
こんにちは、のんピ(@non____97)です。
皆さんはALBでTLS 1.0が含まれているSSLポリシーを使用していたら検出したいなと思ったことはありますか? 私はあります。
ALBやNLBで使用可能なセキュリティポリシーは以下AWS公式ドキュメントにまとまっています。
セキュリティポリシーにはELBSecurityPolicy-2016-08
などTLS 1.0やTLS 1.1など非推奨なTLSの使用を許可しているものもあります。
古いクライアントをサポートするためにあえて選択することもあると思いますが、間違って設定してしまうことも多いと思います。
そのような場合はAWS Configで検出したいところです。
残念ながらAWS ConfigのマネージドルールではALBやNLBのセキュリティポリシーをチェックすることはできません。elb-predefined-security-policy-ssl-check
という、いかにもな名前のマネージドルールがありますが、こちらはCLBしかチェックできません。
ということでカスタムルールを作成して対応します。
いきなりまとめ
- 基本的にはカスタムポリシールールを使いたい
- カスタムポリシールールでサポートしているリソースタイプはAWS Config Resource Schema GitHub Repositoryのresource-typesを確認しよう
- AWS Config RDKで生成したLambda関数のテンプレートを活用すれば、ロジックを少し書くだけでカスタムLambdaルールの実装ができる
ALBのリスナーをチェックするカスタムルールはカスタムLambdaルールである必要がある
作成するカスタムルールはカスタムLambdaルールである必要があります。
AWS Config のカスタムルールは以下の 2 種類あります。
- カスタム Lambda ルール
- カスタムポリシールール
前者はその名の通り、リソースをチェックする Lambda 関数を用意して、その Lambda 関数の評価結果をベースに準拠/非準拠を判断します。
後者は「Guard」と呼ばれる独自のポリシー言語を利用してリソースの評価をするルールです。
基本的には「カスタムポリシールール」を使うべきだと考えます。
理由は以下の通りです。
- Lambda 関数を用意する必要がなくなる
- 定期的なランタイムのバージョンアップや、IAM ロールの権限周りの設計から解放される
- Lambda 関数実行にかかるコストが減る
一方、カスタムポリシールールの懸念点として「サポートされていないリソースタイプがある」が挙げられます。
サポートされているリソースタイプ一覧はAWS Config Resource Schema GitHub Repositoryのresource-types
にまとまっています。
残念ながらALBやNLBのリスナーであるAWS::ElasticLoadBalancingV2::Listener
は2023/11/14時点ではサポートされていません。
そのため、今回はカスタムLambdaルールを作成します。
カスタムLambdaルールの作成
AWS Configのベストプラクティスを確認すると、「AWS Config RDKを使ってカスタムルールを作成する」と記載されています。
15.AWS Config Rule Development Kit (RDK) を使用して、カスタムルールを作成します。
RDK はオーサリングエクスペリエンスを簡素化し、さまざまなリソースタイプのルールを検証します。
DevelopersIOにもAWS Config RDKを使ってカスタムLambdaルールを用意する方法が紹介されています。
AWS Config RDKを使ってLambda関数のテンプレートを生成します。
Lambda関数で行いたい処理は以下のとおりです。
- カスタムLambdaルールのパラメーターとして指定したセキュリティポリシーが存在するか
- 存在しなければ終了
- カスタムLambdaルールトリガー時の
configuration_item
からリスナーのプロトコルを取得し、サポートしているプロトコルに含まれるか- サポートしているプロトコルでなければ
NOT_APPLICABLE
- サポートしているプロトコルでなければ
- カスタムLambdaルールトリガー時の
configuration_item
からリスナーのセキュリティポリシーを取得し、カスタムLambdaルールのパラメーターとして指定したセキュリティポリシーリスト内に含まれるか- 含まれていれば
COMPLIANT
- 含まれていれば
NON_COMPLIANT
- 含まれていれば
Config Ruleのパラメーターはリストで指定できません。そのため、許可したいSSLポリシーはカンマ区切りで指定する想定です。
最終的なコードは以下のとおりです。テンプレートから変更した箇所はハイライトしておきます。
import json import sys import datetime import boto3 import botocore import logging logger = logging.getLogger() logger.setLevel(logging.INFO) try: import liblogging except ImportError: pass ############## # Parameters # ############## # Define the default resource to report to Config Rules DEFAULT_RESOURCE_TYPE = "AWS::ElasticLoadBalancingV2::Listener" # Set to True to get the lambda to assume the Role attached on the Config Service (useful for cross-account). ASSUME_ROLE_MODE = False # Other parameters (no change needed) CONFIG_ROLE_TIMEOUT_SECONDS = 900 # Support protocol SUPPORT_PROTOCOL=['HTTPS', 'TLS'] ############# # Main Code # ############# def evaluate_compliance(event, configuration_item, valid_rule_parameters): """Form the evaluation(s) to be return to Config Rules Return either: None -- when no result needs to be displayed a string -- either COMPLIANT, NON_COMPLIANT or NOT_APPLICABLE a dictionary -- the evaluation dictionary, usually built by build_evaluation_from_config_item() a list of dictionary -- a list of evaluation dictionary , usually built by build_evaluation() Keyword arguments: event -- the event variable given in the lambda handler configuration_item -- the configurationItem dictionary in the invokingEvent valid_rule_parameters -- the output of the evaluate_parameters() representing validated parameters of the Config Rule Advanced Notes: 1 -- if a resource is deleted and generate a configuration change with ResourceDeleted status, the Boilerplate code will put a NOT_APPLICABLE on this resource automatically. 2 -- if a None or a list of dictionary is returned, the old evaluation(s) which are not returned in the new evaluation list are returned as NOT_APPLICABLE by the Boilerplate code 3 -- if None or an empty string, list or dict is returned, the Boilerplate code will put a "shadow" evaluation to feedback that the evaluation took place properly """ ############################### # Add your custom logic here. # ############################### protocol = configuration_item["configuration"]["Protocol"] logger.info('Listener arn : %s',configuration_item["configuration"]["ListenerArn"]) logger.info('Listener protocol : %s', protocol) if not protocol in SUPPORT_PROTOCOL: logger.info('NOT_APPLICABLE') return build_evaluation_from_config_item(configuration_item, "NOT_APPLICABLE") ssl_policy = configuration_item["configuration"]["SslPolicy"] rule_ssl_policies = [x.strip() for x in valid_rule_parameters['sslPolicies'].split(',')] logger.info('Listener SSL policy : %s', ssl_policy) logger.info('Rule SSL policies : %s', rule_ssl_policies) if ssl_policy in rule_ssl_policies: logger.info('COMPLIANT') return build_evaluation_from_config_item(configuration_item, "COMPLIANT") else: logger.info('NON_COMPLIANT') return build_evaluation_from_config_item(configuration_item, "NON_COMPLIANT") def evaluate_parameters(rule_parameters): """Evaluate the rule parameters dictionary validity. Raise a ValueError for invalid parameters. Return: anything suitable for the evaluate_compliance() Keyword arguments: rule_parameters -- the Key/Value dictionary of the Config Rules parameters """ rule_ssl_policies = [x.strip() for x in rule_parameters['sslPolicies'].split(',')] client = boto3.client('elbv2') try : client.describe_ssl_policies(Names=rule_ssl_policies) except client.exceptions.ClientError as e: logger.info('Rule SSL policies : %s', rule_ssl_policies) logger.error(e) raise ValueError from e valid_rule_parameters = rule_parameters return valid_rule_parameters . . (以下略) . .
Lambda関数とカスタムLambdaルールはAWS CDKでデプロイします。
使用したコードは以下リポジトリに保存しています。
動作確認
許可しているセキュリティポリシーを設定した場合
それでは動作確認をしてみます。
AWS CDKデプロイ後、ELBSecurityPolicy-TLS13-1-2-2021-06
のセキュリティポリシーを設定したHTTPSリスナーを持つALBを作成します。
こちらのALBを作成してしばらくすると、コンプライアンスが準拠
であるリスナーを確認できました。
Lambda関数のログは以下のとおりです。COMPLIANT
で判定したことが分かります。
INIT_START Runtime Version: python:3.11.v18 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:20e91e114d5658aaf7ecc7e7bcec28f4aca1368a06e7faa048c90033a9de7f31 START RequestId: 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Version: $LATEST [INFO] 2023-11-13T08:35:34.578Z 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Found credentials in environment variables. [INFO] 2023-11-13T08:35:35.919Z 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Listener arn : arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:listener/app/alb/18cce6b2b289a938/5ecc9c6c361275dc [INFO] 2023-11-13T08:35:35.919Z 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Listener protocol : HTTPS [INFO] 2023-11-13T08:35:35.919Z 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Listener SSL policy : ELBSecurityPolicy-TLS13-1-2-2021-06 [INFO] 2023-11-13T08:35:35.919Z 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Rule SSL policies : ['ELBSecurityPolicy-TLS13-1-2-2021-06', 'ELBSecurityPolicy-TLS13-1-2-Res-2021-06'] [INFO] 2023-11-13T08:35:35.919Z 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb COMPLIANT END RequestId: 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb REPORT RequestId: 0fca4c90-8c7c-47fd-ad96-43b959a7b2cb Duration: 1775.33 ms Billed Duration: 1776 ms Memory Size: 128 MB Max Memory Used: 76 MB Init Duration: 232.23 ms
参考までにAWS::ElasticLoadBalancingV2::Listener
のJSONは以下のとおりです。
{ "version": "1.3", "accountId": "<AWSアカウントID>", "configurationItemCaptureTime": "2023-11-13T07:52:43.753Z", "configurationItemStatus": "ResourceDiscovered", "configurationStateId": "1699861963753", "configurationItemMD5Hash": "", "arn": "arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:listener/app/alb/18cce6b2b289a938/5ecc9c6c361275dc", "resourceType": "AWS::ElasticLoadBalancingV2::Listener", "resourceId": "arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:listener/app/alb/18cce6b2b289a938/5ecc9c6c361275dc", "awsRegion": "us-east-1", "availabilityZone": "Regional", "tags": {}, "relatedEvents": [], "relationships": [], "configuration": { "SslPolicy": "ELBSecurityPolicy-TLS13-1-2-2021-06", "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:loadbalancer/app/alb/18cce6b2b289a938", "DefaultActions": [ { "Type": "forward", "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:targetgroup/test-tg/151945e8b918f93d", "Order": 1, "ForwardConfig": { "TargetGroups": [ { "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:targetgroup/test-tg/151945e8b918f93d", "Weight": 1 } ], "TargetGroupStickinessConfig": { "Enabled": false, "DurationSeconds": 3600 } } } ], "Port": 443, "Certificates": [ { "CertificateArn": "arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/f1941e30-42f8-41cc-8d96-c850b3bb86e8" } ], "Protocol": "HTTPS", "ListenerArn": "arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:listener/app/alb/18cce6b2b289a938/5ecc9c6c361275dc", "AlpnPolicy": [] }, "supplementaryConfiguration": {} }
Config Ruleのリソース一覧に作成したリスナーが表示されない場合は、以下re:Postに従ってトラブルシューティングします。
私の場合はAWS::ElasticLoadBalancingV2::Listener
をAWS Configのレコード設定で有効化していないことにより、時間を溶かしてしまいました。
許可していないセキュリティポリシーを設定した場合
次に、許可していないセキュリティポリシーを設定した場合の動作を確認します。
リスナーのセキュリティポリシーをELBSecurityPolicy-2016-08
に変更します。
すると、カスタムLambdaルールにてリスナーが非準拠状態になりました。
Lambda関数のログは以下のとおりです。NON_COMPLIANT
で判定したことが分かります。
INIT_START Runtime Version: python:3.11.v18 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:20e91e114d5658aaf7ecc7e7bcec28f4aca1368a06e7faa048c90033a9de7f31 START RequestId: 4ff56904-f08c-49fb-a043-73e280bcffc2 Version: $LATEST [INFO] 2023-11-13T09:48:27.153Z 4ff56904-f08c-49fb-a043-73e280bcffc2 Found credentials in environment variables. [INFO] 2023-11-13T09:48:28.655Z 4ff56904-f08c-49fb-a043-73e280bcffc2 Listener arn : arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:listener/app/alb/18cce6b2b289a938/5ecc9c6c361275dc [INFO] 2023-11-13T09:48:28.655Z 4ff56904-f08c-49fb-a043-73e280bcffc2 Listener protocol : HTTPS [INFO] 2023-11-13T09:48:28.730Z 4ff56904-f08c-49fb-a043-73e280bcffc2 Listener SSL policy : ELBSecurityPolicy-2016-08 [INFO] 2023-11-13T09:48:28.730Z 4ff56904-f08c-49fb-a043-73e280bcffc2 Rule SSL policies : ['ELBSecurityPolicy-TLS13-1-2-2021-06', 'ELBSecurityPolicy-TLS13-1-2-Res-2021-06'] [INFO] 2023-11-13T09:48:28.731Z 4ff56904-f08c-49fb-a043-73e280bcffc2 NON_COMPLIANT END RequestId: 4ff56904-f08c-49fb-a043-73e280bcffc2 REPORT RequestId: 4ff56904-f08c-49fb-a043-73e280bcffc2 Duration: 1953.81 ms Billed Duration: 1954 ms Memory Size: 128 MB Max Memory Used: 76 MB Init Duration: 251.05 ms
セキュリティポリシーが設定できないプロトコルの場合
続いて、セキュリティポリシーが設定できないプロトコルの場合の動作を確認します。
ALBにHTTPリスナーを追加します。
リスナーを追加して数分待ちましたが、追加したリスナーのコンプライアンスの判定はされていません。
Lambda関数のログは以下のとおりです。NON_COMPLIANT
で判定したことが分かります。
START RequestId: a4cc46a1-fc1b-4b0c-82e3-b678c42d7d37 Version: $LATEST [INFO] 2023-11-13T10:01:20.328Z a4cc46a1-fc1b-4b0c-82e3-b678c42d7d37 Listener arn : arn:aws:elasticloadbalancing:us-east-1:<AWSアカウントID>:listener/app/alb/18cce6b2b289a938/e737c4f21e402091 [INFO] 2023-11-13T10:01:20.328Z a4cc46a1-fc1b-4b0c-82e3-b678c42d7d37 Listener protocol : HTTP [INFO] 2023-11-13T10:01:20.328Z a4cc46a1-fc1b-4b0c-82e3-b678c42d7d37 NOT_APPLICABLE END RequestId: a4cc46a1-fc1b-4b0c-82e3-b678c42d7d37 REPORT RequestId: a4cc46a1-fc1b-4b0c-82e3-b678c42d7d37 Duration: 549.33 ms Billed Duration: 550 ms Memory Size: 128 MB Max Memory Used: 77 MB
許可するセキュリティポリシーリストにELBがサポートしていないセキュリティポリシーが含まれている場合
最後に、許可するセキュリティポリシーリストにELBがサポートしていないセキュリティポリシーが含まれている場合の動作を確認します。
カスタムLambdaルールのパラメーターsslPolicies
の値をELBSecurityPolicy-TLS13-1-2-2021-06, ELBSecurityPolicy-TLS13-1-2-Res-2021-06
からELBSecurityPolicy-TLS13-1-2-2021-06, ELBSecurityPolicy-TLS13-1-2-Res-2021-06_
に変更します。
設定変更後、カスタムLambdaルールの再評価を行います。
特に既存リスナーのコンプライアンスの状態に変化はありませんでした。
Lambda関数のログは以下のとおりです。パラメーターの値が適していないとのことでエラーになっていますね。
INIT_START Runtime Version: python:3.11.v18 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:20e91e114d5658aaf7ecc7e7bcec28f4aca1368a06e7faa048c90033a9de7f31 START RequestId: 5dd3a786-ba96-4fd9-9d75-949af6f13525 Version: $LATEST [INFO] 2023-11-14T07:24:31.215Z 5dd3a786-ba96-4fd9-9d75-949af6f13525 Found credentials in environment variables. [INFO] 2023-11-14T07:24:36.524Z 5dd3a786-ba96-4fd9-9d75-949af6f13525 Rule SSL policies : ['ELBSecurityPolicy-TLS13-1-2-2021-06', 'ELBSecurityPolicy-TLS13-1-2-Res-2021-06_'] [ERROR] 2023-11-14T07:24:36.524Z 5dd3a786-ba96-4fd9-9d75-949af6f13525 An error occurred (ServiceUnavailable) when calling the DescribeSSLPolicies operation (reached max retries: 4): Internal failure {'internalErrorMessage': 'Parameter value is invalid', 'internalErrorDetails': 'An ValueError was raised during the validation of the Parameter value', 'customerErrorMessage': '', 'customerErrorCode': 'InvalidParameterValueException'} END RequestId: 5dd3a786-ba96-4fd9-9d75-949af6f13525 REPORT RequestId: 5dd3a786-ba96-4fd9-9d75-949af6f13525 Duration: 5498.76 ms Billed Duration: 5499 ms Memory Size: 128 MB Max Memory Used: 75 MB Init Duration: 241.72 ms
カスタムルールを使いこなそう
ALBで指定のセキュリティポリシー以外を使用した場合を検出するAWS Configのカスタムルールを作成してみました。
NLBの場合もTLSリスナーで動作することを確認しています。
基本的にはマネージドルールを使いたいところですが、サポートされていないリソースについてはカスタムルールを使っていきましょう。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!