diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ce1f12..291fc6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,29 +157,41 @@ jobs: PLAN_TXT=plan.txt PLAN_JSON=plan.json - # Run terraform plan - terraform plan -no-color -out=$PLAN_FILE > /dev/null 2> plan_error.txt || echo "PLAN_FAILED=true" >> $GITHUB_ENV - - # Show plan text output - terraform show -no-color $PLAN_FILE > $PLAN_TXT 2>/dev/null || echo "Plan failed" > $PLAN_TXT - - # Remove ANSI color codes - cat $PLAN_TXT | \ - sed 's/`/\\`/g' | \ - tr -d '\r' | \ - sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" \ - > cleaned_plan.txt + if terraform plan -no-color -out=$PLAN_FILE > /dev/null 2> plan_error.txt; then + echo "PLAN_FAILED=false" >> $GITHUB_ENV + terraform show -no-color $PLAN_FILE > $PLAN_TXT + terraform show -json $PLAN_FILE > $PLAN_JSON || true + else + echo "PLAN_FAILED=true" >> $GITHUB_ENV + echo "Plan failed" > $PLAN_TXT + echo "{}" > $PLAN_JSON + fi + + # 디버깅용 출력 + echo "::group::Raw terraform show output" + cat $PLAN_TXT || echo "(empty)" + echo "::endgroup::" + + sed 's/`/\\`/g' $PLAN_TXT | tr -d '\r' | sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" > cleaned_plan.txt PLAN_CONTENT=$(cat cleaned_plan.txt) + PLAN_ERROR=$(cat plan_error.txt || echo "No error captured") - # Save JSON plan for infracost - terraform show -json $PLAN_FILE > $PLAN_JSON || true + if [ -z "$PLAN_CONTENT" ]; then + PLAN_CONTENT="(no changes or output empty)" + fi + + if [ -z "$PLAN_ERROR" ]; then + PLAN_ERROR="(no errors)" + fi - # Output plan content for PR comment { echo "PLAN_CONTENT<> $GITHUB_OUTPUT working-directory: ${{ matrix.dir }} @@ -203,6 +215,11 @@ jobs: ${{ steps.plan.outputs.PLAN_CONTENT }} ``` + ### Plan Error (if any) + ``` + ${{ steps.plan.outputs.PLAN_ERROR }} + ``` + - name: Setup Infracost uses: infracost/actions/setup@v2 diff --git a/prod-team-account/deploy/waf/main.tf b/prod-team-account/deploy/waf/main.tf index e6ae276..f26a121 100644 --- a/prod-team-account/deploy/waf/main.tf +++ b/prod-team-account/deploy/waf/main.tf @@ -12,6 +12,51 @@ provider "aws" { region = "ap-northeast-2" } +# rule 목록 정의 +locals { + managed_rules = { + # 규칙이름 = { 우선순위, AWS 관리형 규칙 이름 } + "CommonRuleSet" = { priority = 10, name = "AWSManagedRulesCommonRuleSet" } + "KnownBadInputs" = { priority = 20, name = "AWSManagedRulesKnownBadInputsRuleSet" } + "SQLiRuleSet" = { priority = 30, name = "AWSManagedRulesSQLiRuleSet" } + "AmazonIpReputation" = { priority = 40, name = "AWSManagedRulesAmazonIpReputationList" } + } +} + +# IP 기반 요청 제한 규칙 생성 +resource "aws_wafv2_rule_group" "rate_limit_rule" { + name = "${var.project_name}-rate-limit-rule" + scope = "REGIONAL" + capacity = 50 + rule { + name = "RateLimit5Min2000" + priority = 10 + + action { + block {} + } + + statement { + rate_based_statement { + limit = 2000 + aggregate_key_type = "IP" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "RateLimitMetric" + sampled_requests_enabled = true + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${var.project_name}-rate-limit-metric" + sampled_requests_enabled = true + } +} + # WAF resource "aws_wafv2_web_acl" "alb_waf" { name = "${var.project_name}-alb-waf" @@ -22,32 +67,76 @@ resource "aws_wafv2_web_acl" "alb_waf" { allow {} } - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "waf-alb-metric" - sampled_requests_enabled = true + dynamic "rule" { + for_each = local.managed_rules + content { + name = "AWS-${rule.value.name}" + priority = rule.value.priority + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + vendor_name = "AWS" + name = rule.value.name + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "waf-${rule.value.name}-metric" + sampled_requests_enabled = true + } + } } + # 생성된 규칙을 사용하여 요청 제한 규칙 추가 rule { - name = "AWS-AWSManagedRulesCommonRuleSet" - priority = 1 - override_action { - none {} - } + name = "RateLimitRule" + priority = 50 + statement { - managed_rule_group_statement { - vendor_name = "AWS" - name = "AWSManagedRulesCommonRuleSet" + rule_group_reference_statement { + arn = aws_wafv2_rule_group.rate_limit_rule.arn } } + visibility_config { cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesCommonRuleSet" + metric_name = "RateLimitRuleMetric" sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "waf-alb-metric" + sampled_requests_enabled = true + } + tags = { Name = "${var.project_name}-alb-waf" } -} \ No newline at end of file +} + +# S3로 로그 전송하도록 설정 +resource "aws_wafv2_web_acl_logging_configuration" "waf_logging" { + resource_arn = aws_wafv2_web_acl.alb_waf.arn + log_destination_configs = ["arn:aws:s3:::${var.bucket_name}"] + + logging_filter { + default_behavior = "DROP" + filter { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition { + action_condition { + action = "BLOCK" + } + } + } + } +} + diff --git a/prod-team-account/deploy/waf/variables.tf b/prod-team-account/deploy/waf/variables.tf index 7c839dd..eb9fcfc 100644 --- a/prod-team-account/deploy/waf/variables.tf +++ b/prod-team-account/deploy/waf/variables.tf @@ -2,4 +2,10 @@ variable "project_name" { description = "The name of the project" type = string default = "cloudfence" +} + +variable "bucket_name" { + description = "The name of the S3 bucket for WAF logs" + type = string + } \ No newline at end of file