Configure EC2 logs to Cloudwatch

Configure EC2 logs to Cloudwatch

with Terraform

Table of contents

No heading

No headings in the article.

In this article, ec2 (Amazon Linux) will be configured with Cloud Watch to send its logs.

All the resources will be created with Terraform.

Let's start with VPC, subnet, route tables & etc..

VPC.tf

resource "aws_vpc" "vpc" {
  cidr_block       = var.vpc_cidr
  instance_tenancy = "default"
  tags = {
    Name = "${var.environment}-vpc"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id
  depends_on = [
    aws_vpc.vpc
  ]
  tags = {
    "Name" = "${var.environment}-igw"
  }
}

resource "aws_subnet" "subnet" {
  vpc_id     = aws_vpc.vpc.id
  cidr_block = var.subnet_cidr
  tags = {
    Name = "${var.environment}-subnet"
  }
}

resource "aws_route_table" "public-rt" {
  vpc_id = aws_vpc.vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
  tags = {
    "Name" = "${var.environment}-public-route-table"
  }
}

resource "aws_route_table_association" "rta-sub" {
  subnet_id      = aws_subnet.subnet.id
  route_table_id = aws_route_table.public-rt.id
}

resource "aws_security_group" "sg" {
  name = "${var.environment}-demo-sg"
  vpc_id = aws_vpc.vpc.id
  ingress {
    from_port = var.security_group_open_port[0]
    to_port   = var.security_group_open_port[1]
    protocol  = "tcp"
    cidr_blocks = [local.workstation_external_ip]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = -1
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group_rule" "access_for_ip" {
  for_each    = toset(var.security_group_allow_ip)
  type        = "ingress"
  from_port   = var.security_group_open_port[0]
  to_port     = var.security_group_open_port[1]
  protocol    = "tcp"
  cidr_blocks = [each.value]
  security_group_id = aws_security_group.sg.id
}

In security group block, ingress cidr_block is configured to local.workstation_external_ip. This will be your local-machine public IP, which you can see at whatismyipaddress.com

Now, we have to get the IP through terraform.

get_external_ip.tf

data "http" "workstation-external-ip" {
  url = "http://ipv4.icanhazip.com"
}

locals {
  workstation_external_ip = "${chomp(data.http.workstation-external-ip.body)}/32"
}

ec2.tf

resource "tls_private_key" "ssh" {
  algorithm = "RSA"
  rsa_bits  = "4096"
}
resource "local_file" "ssh_private_key" {
  content         = tls_private_key.ssh.private_key_pem
  filename        = "./tls/private.pem"
  file_permission = "0600"
}
resource "aws_key_pair" "tf-ssh-key" {
  key_name   = "${var.environment}-ssh-key"
  public_key = tls_private_key.ssh.public_key_openssh
}


resource "aws_instance" "ec2" {
  count = var.instance_count
  ami                    = var.instance_ami
  instance_type          = var.instance_type
  key_name               = aws_key_pair.tf-ssh-key.key_name
  vpc_security_group_ids = [aws_security_group.sg.id]
  tags = {
    Name = "${var.environment}-vm"
  }
  associate_public_ip_address = true
  subnet_id                   = aws_subnet.subnet.id
  iam_instance_profile = aws_iam_instance_profile.profile.id
  user_data = <<-EOF
  #!/bin/bash
  yum update -y
  yum install -y awslogs
  systemctl start awslogsd
  EOF
}

cloudwatch.tf

resource "aws_iam_policy" "logs-push-policy" {
  name        = "cw-policy-to-push-logs-to-ec2"
  path        = "/"
  description = "cw-policy-to-push-logs-to-ec2"

  policy = jsonencode({
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogStreams"
    ],
      "Resource": [
        "arn:aws:logs:*:*:*"
    ]
  }
 ]
})
}
resource "aws_iam_role" "ec2-cw-role" {
    name = "cw-ec2-logs-role"
    assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}
resource "aws_iam_role_policy_attachment" "attach-cw-role" {
    role = aws_iam_role.ec2-cw-role.id
    policy_arn = aws_iam_policy.logs-push-policy.id
}
resource "aws_iam_instance_profile" "profile" {
    name = "ec2-cw-profile"
    role = aws_iam_role.ec2-cw-role.id
}

variables.tf

variable "region" {
    default = "us-east-1"
}
variable "environment" {
    default = "dev"
}
variable "vpc_cidr" {
    default = "18.0.0.0/16"
}

variable "subnet_cidr" {
    default = "18.0.0.0/24"
}

variable "security_group_open_port" {
    default = [22, 22]
}
variable "security_group_allow_ip" {
    default = ["180.0.0.0/32"]
}

variable "instance_count" {
    default = 1
}

variable "instance_type" {
    default = "t2.micro"
}
variable "instance_ami" {
    default = "ami-026b57f3c383c2eec" ## amazon linux
}

outputs.tf

output "local_workstation_ip" {
  value = local.workstation_external_ip
}
output "ec2_public_ip" {
  #   value       = aws_instance.ec2[0].public_ip
  value = aws_instance.ec2[*].public_ip
}

Run terraform commands to create resourcs

$ terraform init
$ terraform plan
-> Plan: 15 to add, 0 to change, 0 to destroy.

$ terraform apply --auto-approve
-> 
Apply complete! Resources: 15 added, 0 changed, 0 destroyed.

Outputs:

ec2_public_ip = [
  "52.87.50.72",
]
local_workstation_ip = "***.49.55.31/32"

Once resources are created, check In-bound Rule in Security Groups

go to AWS Dashboard > VPC management > Security Groups sg_1.png

Now, try ssh log-in with private key generated in tls folder. Get public IP from outputs.

$ ssh -i ./tls/private.pem ec2-user@52.87.50.72

Check Cloudwatch logs go to AWS Dashboard > CloudWatch > Log groups > /var/log/messages

cw1.JPG

Destroy the resources once done

$ terraform destroy

-> Destroy complete! Resources: 15 destroyed.

Terraform code will be available at Github.

Thanks for reading.... :)