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)でした!