はじめに
稼働時間外にシステムへアクセスがあったユーザに対して、稼働時間外であることを伝えるページを表示させるため、AWS Lambda+EventBridgeにて自動化してみました。
今回のケースに限らず、他の処理自動化にも流用することができると思います。
環境
- macOS Sonoma 14.1
- terraform 1.5.2
目次
- イメージ図
- ELBリスナールールの作成
- Lambdaの設定
- Lambda関数の作成
- iamロールの設定
- EventBridgeの設定
- EventBridgeの作成
- targetの設定
- lambda permissionの設定
- 補足
イメージ図
対象システムにはELBを配置しているため、ELBリスナールールにて稼働時間外メッセージ表示、管理を行います。
ELBリスナールールの作成
まずは、稼働時間外メッセージ表示のリスナールールを作成します。
# aws_lb_listener_rule.change_lb_listener_rule_priority_foo_close:
resource "aws_lb_listener_rule" "change_lb_listener_rule_priority_foo_close" {
listener_arn = aws_lb_listener.foo-https.arn
priority = 1000
tags = {}
action {
order = 1
type = "fixed-response"
fixed_response {
content_type = "text/html"
message_body = <<-EOT
<!doctype html>
<head>
<meta charset="utf-8">
<title>システム稼働時間外</title>
</head>
<body>
<header>
<div id="content">
<section class="row">
<div class="col-full">
<p>
当サイトはシステム稼働時間外のためアクセスできません。<br>
稼働時間まで今しばらくお待ちください。<br>
</p>
<p>
This site is currently out of operation hours<br>
Please wait a while until the operation time.<br>
</p>
<h1>稼働時間<br>08:00 〜 19:00</h1>
</div>
</section>
<section class="row">
<div class="col-full">
<p>
<span style="font-weight:bold;">株式会社foo</span><br /> <span style="font-size:70%;">© foo Inc.</span>
</p>
</div>
</section>
</div>
</body>
</html>
EOT
status_code = "503"
}
}
condition {
host_header {
values = [
"foo.com",
"bar.com",
]
}
}
lifecycle {
ignore_changes = [
priority,
action[0].fixed_response[0].message_body
]
}
}
Lamdbaの設定
Lambda関数の作成
続いて、Lambda関数を作成します。
作成したELBリスナールールのpriority
順位を変更することにより、稼働時間外メッセージの表示・非表示を切り替えるための関数になります。
import boto3
import json
def lambda_handler(event, context):
print(json.dumps(event))
client = boto3.client('elbv2')
try:
# event値取得
listener_rule_arn = event['resources'][0]
priority_value = int(event['detail']['priority'][0])
# リスナールールのpriority変更
response = client.set_rule_priorities(
RulePriorities=[
{
'RuleArn': listener_rule_arn,
'Priority': priority_value
},
]
)
return {
'statusCode': 200,
'body': f'Successfully updated rule priority for {listener_rule_arn} to {priority_value}'
}
except Exception as e:
raise e
lambda_function.py
はterraformとは別ディレクトリにて管理していたため、data.tf
内のpath.module
にてパスを指定し、参照するにようにしています。
# aws_lambda_function.change_lb_listener_rule_priority:
resource "aws_lambda_function" "change_lb_listener_rule_priority" {
architectures = [
"x86_64",
]
function_name = "change_lb_listener_rule_priority"
filename = data.archive_file.change_lb_listener_rule_priority.output_path
handler = "lambda_function.lambda_handler"
layers = []
memory_size = 128
package_type = "Zip"
publish = false
source_code_hash = data.archive_file.change_lb_listener_rule_priority.output_base64sha256
reserved_concurrent_executions = -1
role = aws_iam_role.LambdaRole_change_lb_listener_rule_priority.arn
runtime = "python3.9"
tags = {}
timeout = 120
ephemeral_storage {
size = 512
}
tracing_config {
mode = "PassThrough"
}
vpc_config {
security_group_ids = [
aws_security_group.lambda.id,
]
subnet_ids = [
aws_subnet.protect-1a.id,
aws_subnet.protect-1c.id,
aws_subnet.protect-1d.id,
]
}
}
data "archive_file" "change_lb_listener_rule_priority" {
type = "zip"
output_path = "${path.module}/../../lambda_functions/change_lb_listener_rule_priority/change_lb_listener_rule_priority.zip"
source_file = "${path.module}/../../lambda_functions/change_lb_listener_rule_priority/lambda_function.py"
}
iamロールの設定
Lambda関数に対してELBリスナールールのpriority
変更を実施できる権限を付与します。
# aws_iam_role.LambdaRole_change_lb_listener_rule_priority:
resource "aws_iam_role" "LambdaRole_change_lb_listener_rule_priority" {
assume_role_policy = jsonencode(
{
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
},
]
Version = "2012-10-17"
}
)
force_detach_policies = false
managed_policy_arns = [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",
]
max_session_duration = 3600
name = "LambdaRole_change_lb_listener_rule_priority"
path = "/service-role/"
tags = {}
inline_policy {
name = "change_lb_rule_priority_policy"
policy = jsonencode(
{
Statement = [
{
Action = [
"elasticloadbalancing:SetRulePriorities"
]
Effect = "Allow"
Resource = [
"arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*/*",
"arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*/*"
]
},
]
Version = "2012-10-17"
}
)
}
}
EventBridgeの設定
EventBridgeの作成
続いて、EventBridgeを作成します。
特定時刻にLambda関数を自動実行するようにします。
下記では7:55と19:00に実行するように設定しています。
# aws_cloudwatch_event_rule.change_lb_listener_rule_priority_open_0755JST:
resource "aws_cloudwatch_event_rule" "change_lb_listener_rule_priority_open_0755JST" {
description = "システム稼働時間外メッセージを非表示する(LBリスナールールプライオリティ変更)"
event_bus_name = "default"
is_enabled = true
name = "change_lb_listener_rule_priority_open_0755JST"
schedule_expression = "cron(55 22 ? * SUN-THU *)"
tags = {}
}
# aws_cloudwatch_event_rule.change_lb_listener_rule_priority_close_1900JST:
resource "aws_cloudwatch_event_rule" "change_lb_listener_rule_priority_close_1900JST" {
description = "システム稼働時間外メッセージを表示する(LBリスナールールプライオリティ変更)"
event_bus_name = "default"
is_enabled = true
name = "change_lb_listener_rule_priority_close_1900JST"
schedule_expression = "cron(00 10 ? * MON-FRI *)"
tags = {}
}
targetの設定
EventBridgeにtarget設定をすることにより、対象のLambda関数を実行します。
この時にEventBridge側からはevent値としてpriority
をLambda関数に渡しています。
7:55ではpriority
の優先度を下げて時間外メッセージを非表示に、19:00ではpriority
の優先度を上げて時間外メッセージを表示するようにします。
# aws_cloudwatch_event_target.change_lb_listener_rule_priority_open_0755JST:
resource "aws_cloudwatch_event_target" "change_lb_listener_rule_priority_open_0755JST" {
arn = aws_lambda_function.change_lb_listener_rule_priority.arn
rule = aws_cloudwatch_event_rule.change_lb_listener_rule_priority_open_0755JST.name
event_bus_name = "default"
input = jsonencode({
"detail" : {
"priority" : [
"1000"
]
},
"resources" : [
aws_lb_listener_rule.change_lb_listener_rule_priority_close.arn
]
})
}
# aws_cloudwatch_event_target.change_lb_listener_rule_priority_close_1900JST:
resource "aws_cloudwatch_event_target" "change_lb_listener_rule_priority_close_1900JST" {
arn = aws_lambda_function.change_lb_listener_rule_priority.arn
rule = aws_cloudwatch_event_rule.change_lb_listener_rule_priority_close_1900JST.name
event_bus_name = "default"
input = jsonencode({
"detail": {
"priority": [
"1"
]
},
"resources": [
aws_lb_listener_rule.change_lb_listener_rule_priority_close.arn
]
})
}
lambda permissionの設定
作成したEventBridgeがLambda関数を実行できるようlambda permissionにて実行権限を付与します。
locals.tf
にてリスト化し、for_each
でまとめて実行しています。
# aws_lambda_permission.events:
resource "aws_lambda_permission" "events" {
for_each = { for i in local.events_lambda_permission_list : "${i[0].name}-${i[1]}" => i }
statement_id = "AllowExecutionFromEventBridge-${each.value[0].name}"
action = "lambda:InvokeFunction"
function_name = each.value[1]
principal = "events.amazonaws.com"
source_arn = each.value[0].arn
}
# locals:
locals {
events_lambda_permission_list = [
[aws_cloudwatch_event_rule.change_lb_listener_rule_priority_open_0755JST, aws_lambda_function.change_lb_listener_rule_priority.function_name],
[aws_cloudwatch_event_rule.change_lb_listener_rule_priority_close_1900JST, aws_lambda_function.change_lb_listener_rule_priority.function_name],
]
}
補足
※ディレクトリ構成やterraformリソース管理の関係で一部コードはdata.tf
等による追加定義や修正が必要かもしれませんが、ご容赦ください。