본문 바로가기

AWS

[AWS] Multi Account에서 IAM 내역 감사하기 #3 - 테라폼으로 인프라 프로비저닝 2

반응형

https://nyyang.tistory.com/145

 

[AWS] Multi Account에서 IAM 내역 감사하기 #2 - 테라폼으로 인프라 프로비저닝

1. 개요 Multi Account 환경에서 IAM 관련 Action에 대한 감사를 하기 위해서는 Log들을 Security 관련 Account에서 수집해야 한다. 이에 대한 인프라 프로비저닝은 Terraform을 사용하였다. (부분 부분 웹 콘솔

nyyang.tistory.com

 

위 글에 이어서 작성하는 글이며, Security Account 외 다른 어카운트들에서 동일한 Resource를 배포하기 위한 Terraform 코드이다.

 

가장 중요한 부분은 Provider Alias를 이해하는 부분이다.

아래 글을 읽으면 빠르게 이해가 될 것이라고 생각한다.

 

https://blog.outsider.ne.kr/1399

 

Terraform에 다중 Provider 사용하기 :: Outsider's Dev Story

AWS, GCP 등 클라우드를 사용할 때 보통 주로 사용하는 리전이 정해져 있다. 일부 Route53이나 CloudFront처럼 리전이 정해져 있지 않은 서비스도 있지만, 대다수 서비스는 리전을 선택해서 사용해야 하

blog.outsider.ne.kr

 


1. Structure (구조)

각 Account에 대해 동일한 Resource를 배포하기 위해 Terraform을 사용했고, 결론적으로 얘기하자면 Provider Alias 부분에 대해 for_each 등의 반복문을 사용할 수 있을 줄 알았는데, 의외로 안되는 것 같았다... 흠

 

(혹시 아시는분 알려주시면 감사하겠습니다.)

2. 파일 설명

반복문으로 처리하지 못해 코드가 좀 길음.

 

 

 

1. _backend.tf

Backend를 S3로 지정하였다.

 

여기서 Profile은 _provider.tf 파일에서 정의해주었는데, 어떤 AWS 어카운트에 대해서 수행할 것인지 정의해주면 Terraform은 _provider.tf의 Provider Alias에 대한 인증 및 인가를 수행한 뒤 backend 설정을 하게 된다.

terraform {
  backend "s3" {
    bucket         = "xxxx-xxxx-xxxx-tfstate"
    dynamodb_table = "xxxx-xxxx-xxxx-terraform-lock-table"
    encrypt        = true
    key            = "xxxx-xxxx/xxxx-xxxx/terraform.tfstate"
    profile        = "xxxx-xxxx"
    region         = "ap-northeast-2"
  }
}

 

2. _provider.tf

~/.aws/credentials 파일에서 profile을 찾아서 인증을 수행하고, Resource를 생성할 때는 alias를 설정한 뒤, Resource 혹은 Data Block에서 profile로 접근하면 된다.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.8.0"
    }
  }
}

// a Account
provider "aws" {
  alias                    = "a"
  profile                  = format("xxxx-%s", "a")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

// b Account
provider "aws" {
  alias                    = "b"
  profile                  = format("xxxx-%s", "b")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

// c Account
provider "aws" {
  alias                    = "c"
  profile                  = format("xxxx-%s", "c")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

// DI Account
provider "aws" {
  alias                    = "d"
  profile                  = format("xxxx-%s", "d")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

// d Account
provider "aws" {
  alias                    = "e"
  profile                  = format("xxxx-%s", "e")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

// e Account
provider "aws" {
  alias                    = "g"
  profile                  = format("xxxx-%s", "g")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

// f Account
provider "aws" {
  alias                    = "f"
  profile                  = format("xxxx-%s", "f")
  region                   = local.region
  shared_credentials_files = [local.cred_file]
}

 

3. _data.tf

A Account -> B Account로 접근하기 위해서는 양측 간 Policy에 대해 명시적 Allow가 되어 있어야 한다.

이 때문에 각 Account에서 Security Account의 EventBus로 PutEvents를 Allow해주는 Policy를 data 블록으로 생성하였음

data "aws_iam_policy_document" "a" {
  provider = aws.a

  statement {
    sid       = "ssss"
    effect    = "Allow"
    resources = [local.security_account_event_bus_arn]
    actions   = ["events:PutEvents"]
  }
}

data "aws_iam_policy_document" "b" {
  provider = aws.b

  statement {
    sid       = "ssss"
    effect    = "Allow"
    resources = [local.security_account_event_bus_arn]
    actions   = ["events:PutEvents"]
  }
}

data "aws_iam_policy_document" "c" {
  provider = aws.c

  statement {
    sid       = "ssss"
    effect    = "Allow"
    resources = [local.security_account_event_bus_arn]
    actions   = ["events:PutEvents"]
  }
}

data "aws_iam_policy_document" "d" {
  provider = aws.d

  statement {
    sid       = "ssss"
    effect    = "Allow"
    resources = [local.security_account_event_bus_arn]
    actions   = ["events:PutEvents"]
  }
}

data "aws_iam_policy_document" "e" {
  provider = aws.e

  statement {
    sid       = "ssss"
    effect    = "Allow"
    resources = [local.security_account_event_bus_arn]
    actions   = ["events:PutEvents"]
  }
}

data "aws_iam_policy_document" "f" {
  provider = aws.f

  statement {
    sid       = "ssss"
    effect    = "Allow"
    resources = [local.security_account_event_bus_arn]
    actions   = ["events:PutEvents"]
  }
}

 

4. _shared_vars.tf, _vars.tf, _terraform.atuo.tfvars

: 이전 글에서 용도 확인 가능, 생략

 

5. eventPattern.json , _local.tf

 

eventPattern을 설정한 뒤, local 변수를 통해 사용할 수 있도록 설정하였다.

{
  "source": [
    "aws.iam"
  ],
  "detail-type": [
    "AWS API Call via CloudTrail"
  ],
  "detail": {
    "eventSource": [
      "iam.amazonaws.com"
    ],
    "eventName": [
      "AddRoleToInstanceProfile",
      "AddUserToGroup",
      "AttachGroupPolicy",
      "AttachRolePolicy",
      "AttachUserPolicy",
      "ChangePassword",
      "CreateAccessKey",
      "CreateGroup",
      "CreateInstanceProfile",
      "CreateLoginProfile",
      "CreateOpenIDConnectProvider",
      "CreatePolicy",
      "CreateRole",
      "CreateServiceLinkedRole",
      "CreateUser",
      "CreateVirtualMFADevice",
      "DeactivateMFADevice",
      "DeleteAccessKey",
      "DeleteGroup",
      "DeleteGroupPolicy",
      "DeleteInstanceProfile",
      "DeleteLoginProfile",
      "DeletePolicy",
      "DeletePolicyVersion",
      "DeleteRole",
      "DeleteRolePolicy",
      "DeleteServerCertificate",
      "DeleteServiceLinkedRole",
      "DeleteSSHPublicKey",
      "DeleteUser",
      "DeleteUserPolicy",
      "DeleteVirtualMFADevice",
      "DetachGroupPolicy",
      "DetachRolePolicy",
      "DetachUserPolicy",
      "EnableMFADevice",
      "PutGroupPolicy",
      "PutRolePolicy",
      "PutUserPolicy",
      "RemoveClientIDFromOpenIDConnectProvider",
      "RemoveRoleFromInstanceProfile",
      "RemoveUserFromGroup",
      "ResetServiceSpecificCredential",
      "ResyncMFADevice",
      "UpdateAccessKey",
      "UpdateAssumeRolePolicy",
      "UpdateGroup",
      "UpdateLoginProfile",
      "UpdateRole",
      "UpdateServiceSpecificCredential",
      "UpdateSigningCertificate",
      "UpdateSSHPublicKey",
      "UpdateUser",
      "CreatePolicyVersion"
    ]
  }
}

 

이렇게 하면 다른 파일에서 local.event_pattern 으로 해당 이벤트 패턴을 설정할 수 있게 될 것이다.

locals {
  prefix    = format("%s-security-audit", "xxxx")
  cred_file = var.cred_file
  region    = var.region

  tags = merge(var.tags,
    {
      Team = var.team
  })

  security_account_event_bus_arn = var.security_account_event_bus_arn

  event_pattern = file("./config/eventPattern.json")
}

 

 

6. event_rule.tf

동일한 Resource가 반복되기 때문에 6개 중 1개만 글에 포함하였다.

resource "aws_cloudwatch_event_rule" "a" {
  name        = "${local.prefix}-event-rule"
  description = "Capture IAM API Actions for security audit"
  provider    = aws.a

  event_pattern = local.event_pattern
}

 

7. event_target.tf

arn : 이 부분은 Security Account의 EventBus ARN을 설정해주면 된다.

resource "aws_cloudwatch_event_target" "a" {
  arn      = local.security_account_event_bus_arn
  provider = aws.a

  rule     = aws_cloudwatch_event_rule.a.name
  role_arn = aws_iam_role.prod.arn

  target_id = "SendToCloudWatchLogs"
  depends_on = [
    aws_iam_role.a
  ]
}

 

8. iam_role.tf

iam role을 생성한 뒤 data block에서 기 생성한 iam policy를 attach해주었다.

// a
resource "aws_iam_role" "a" {
  name     = format("Amazon-EventBridge-to-sec-%s-iam-role", "a")
  provider = aws.a

  path               = "/"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "a" {
  role     = aws_iam_role.a.id
  provider = aws.a

  policy = data.aws_iam_policy_document.a.json
}

 

배포한 리소스 내역은 event bridge 밖에 없지만 event pattern 및 iam role 까지 추가적으로 필요하였다.

 

EventBridge
- Event Pattern : IAM Through CloudTrail
- Target : Security 계정의 EventBridge

 

 

다음으로는  EventRule -> SNS -> Lambda -> Slack으로 Notification을 보내기 위한 메커니즘 및 Python Code에 대해 정리해볼 생각이다.


아래의 글과 메커니즘은 동일하나, 테스트를 진행하면서 추가적으로 고려해야 할 사항을 발견하여 그에 대해서 좀 정리해보도록 하겠다.

(IAM Role, IAM User에서 각각 userIdentity 부분이 달라서 이 부분을 고려하지 않으면 Slack Noti에서 오류가 발생해 노티가 전달되지 않을수도 있다.)

 

https://nyyang.tistory.com/126

 

[Lambda] IAM, SG 변경 사항을 Slack으로 알람 받기

개요 IAM 혹은 Security Group에서의 변경 사항이 감지되면 Slack으로 통보를 받고 싶습니다. ⇒ Notification을 받는 방법은 여러 가지가 있을 것이다. AWS Config를 통해 감지할 수도 있고, 필자의 방식대로

nyyang.tistory.com

 

반응형