cloudwithbass

[Terraform] 테라폼 연습하기 5: security group 본문

Terraform

[Terraform] 테라폼 연습하기 5: security group

여영클 2024. 8. 31. 16:36

현재까지 구성한 인프라

지난 포스팅에서 ec2 인스턴스를 생성했습니다.

이번 포스팅에서는, ec2 구성 시 반드시 확인해야 하는 보안 그룹에 대해 알아보겠습니다.

보안 그룹의 인그레스 규칙이 설정되지 않으면, 라우팅 테이블이 제대로 구성되어 있어도 트래픽이 인스턴스에 도달할 수 없습니다. 

 

이번 포스팅에선 테라폼으로 security group을 구성합니다.

소스 코드는 다음 github 주소에서 확인 가능합니다.

https://github.com/Dminus251/practice-terraform/tree/main/demo05-sg

목차


 

    1. 보안 그룹 모듈 구성하기

     

    modules/t-aws-sg/main.tf

    보안 그룹 모듈입니다.

    aws_security_group 테라폼 문서를 참조해 작성합니다.

    '코드를 어떻게 짜야 간결성과 재사용성을 높일 수 있을까?' 생각하다가 object 타입의 변수를 사용하기로 했습니다.

     

    2024.09.06 수정: 추후 aws_security_group_rule이라는 리소스를 알게 됐고, 이 방법이 모듈화에 더 편리해서 demo07부터 aws_security_group_rule 리소스를 사용하도록 변경했습니다.

    resource "aws_security_group" "sg" {
      name = var.sg-name
      vpc_id = var.sg-vpc_id
    
      ingress {
        from_port       = var.ingress["from_port"]
        to_port         = var.ingress["to_port"]
        protocol        = var.ingress["protocol"]
        cidr_blocks     = var.ingress["cidr_blocks"]
        security_groups = var.ingress["security_groups"]
      }
      
      #나가는 건 딱히 제한하지 않아도 될 듯..?
      egress {
        from_port        = 0
        to_port          = 0
        protocol         = "-1"
        cidr_blocks      = ["0.0.0.0/0"]
      }
    }

     

    modules/t-aws-sg/vars.tf

    variable "sg-vpc_id"{
      type = string
    }
    
    variable "ingress" {
      type = object({
        from_port = number,
        to_port = number,
        protocol = string,
        cidr_blocks = list(string),
        security_groups = list(string),
      })
    }
    
    variable "sg-name" {
      type = string
    }

    상술했듯 ingress 변수를 object 타입으로 선언했습니다.

    object 타입은 map 타입 처럼 중괄호를 사용하지만,  주로 리소스의 attribute를 나타내기 위한 데이터 구조입니다.

    따라서 map과 다르게 변수의 type이 하나로 강제되지 않고 number, string, list(string) 등을 사용할 수 있습니다.


    modules/t-aws-sg/output.tf

    output "sg-id" {
      value = aws_security_group.sg.id
    }

    보안 그룹을 ec2 instance에 할당하기 위해 output으로 보안 그룹의 id를 내보냅니다.


    2. 루트 모듈 구성하기

    이해를 위해 main.tf를 보기 전에 vars.tf부터 확인하겠습니다.


    vars.tf

    루트 모듈의 vars.tf 중 일부입니다.

    variable "sg-ingress" {
      type = object({
        from_port       = number,
        to_port         = number,
        protocol        = string,
        cidr_blocks     = list(string),
        security_groups = list(string),
      })
    
      default = {
          from_port       = 22,
          to_port         = 22,
          protocol        = "tcp",
          cidr_blocks     = ["0.0.0.0/0"],
          security_groups = []
      }
    }

    sg-ingress라는 변수를 선언했습니다.

    security group의 ingress 값으로 사용할 변수이며,  default 값으로 모든 트래픽에서 온 22번 포트의 tcp 통신을 허용합니다.

    22 포트는 ssh 연결에 사용됩니다.

     


    main.tf

    먼저 달라진 부분은 ec2 instance 모듈의 ec2-sg 변수를 추가한 점입니다.

    module "ec2_public" {
      # other configurations ...
      ec2-sg       = [module.sg-public-allow_ssh.sg-id]
    }
    
    
    module "ec2_private" {
      # other configurations ...
      ec2-sg       = [module.sg-private-allow_ssh.sg-id]
    }

     

    밑에 선언할 보안 그룹 모듈의 id를 사용합니다.

    aws_instance의 테라폼 문서를 보면, 보안 그룹은 list(string) 타입으로 할당되어야 하기 때문에 리스트에 id를 넣습니다.

    (당연히 ec2 instance 모듈에서 security group variable을 선언해야 합니다.)

     

    다음으로 

    public subnet과 private subnet의 보안 그룹 내용이 다르기 때문에 보안 그룹 모듈을 두 개생성했습니다.

    먼저 public subnet의 보안 그룹 모듈입니다.

    #public subnet의 instance에 할당. 0.0.0.0/0에서 ssh를 허용함
    module "sg-public-allow_ssh" { #0.0.0.0/0에서 ssh 허용
      source = "./modules/t-aws-sg"
      sg-vpc_id = module.vpc.vpc-id
      ingress = var.sg-ingress
      sg-name = "sg_public" #sg 이름에 하이픈('-') 사용 불가
    }

    ingress의 sg-ingress 변수의 default 값을 그대로 사용합니다.

    public subnet에 사용할 보안 그룹이므로 default인 0.0.0.0/0에서 22번 포트의 tcp 프로토콜을 허용하도록 했습니다.

     

    만약 default 값을 이용하고 싶지 않은 경우, 아래 private subnet의 sg 모듈처럼 코드를 구성하면 됩니다.

    #private subnet의 instanec에 할당. public subnet의 cidr에서의 ssh 허용
    module "sg-private-allow_ssh" { #public subnet에서의 ssh 허용
      source = "./modules/t-aws-sg"
      sg-vpc_id = module.vpc.vpc-id
      ingress = {
        from_port   = var.sg-ingress.from_port
        to_port     = var.sg-ingress.to_port
        protocol    = var.sg-ingress.protocol
        cidr_blocks = var.public_subnet-cidr
        security_groups = var.sg-ingress.security_groups
      }
      sg-name = "sg_private"
    }

    ingress에서 cidr_block을 public subnet의 cidr로 구성했습니다.

    나머지 ingress 요소들은 default 값을 사용했습니다.

     

    따라서 이 보안 그룹을 할당 받은 ec2 instance는 public subnet의 22번 포트로 온 트래픽만 인바운드로 허용하게 됩니다.


    3. 테스트

    mobaXterm을 이용해 ssh 연결이 잘 되는지 테스트하겠습니다.


    3-1. localhost에서 private subnet으로 접속

    당연히 로컬 호스트에서 private subnet의 private ip address로 ssh 연결은 불가능합니다.


    3-2. public subnet에서 private subnet으로 접속

    localhost에서 public subnet의 인스턴스와 ssh 연결한 상태입니다.

    private-0 인스턴스의 private ip는 10.0.0.188/24입니다.

    아래 사진처럼 정상적으로 private subnet의 인스턴스와 ssh 연결에 성공한 것을 확인했습니다.

     

     

    다음은 private-1 인스턴스입니다.

    이 인스턴스의 ip는 10.0.2.109/24고, 마찬가지로 ssh 연결에 성공했습니다.