diff --git a/.github/workflows/create-backend.yml b/.github/workflows/create-backend.yml new file mode 100644 index 0000000..c069c26 --- /dev/null +++ b/.github/workflows/create-backend.yml @@ -0,0 +1,55 @@ +name: Create Backend S3 Bucket + +on: + workflow_dispatch: + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + AWS_REGION: us-east-1 + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + +jobs: + create-backend: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Comment backend block + run: sed -i '/backend "s3"/,/}/s/^/# /' backend/main.tf + + - name: Set up Terraform + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 + with: + terraform_version: '1.10.4' + + - name: Terraform apply + run: | + cd backend + terraform init + terraform apply -auto-approve + + - name: Uncomment backend block + run: sed -i '/backend "s3"/,/}/s/^# //' backend/main.tf + + - name: Migrate state to S3 bucket + run: terraform init -migrate-state + + slack-notify: + needs: + - create-backend + if: always() + runs-on: ubuntu-latest + steps: + - name: Slack notification + uses: come25136/workflow-notification-for-slack@main + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/env/dev/backend.tf b/env/dev/backend.tf new file mode 100644 index 0000000..3eb8742 --- /dev/null +++ b/env/dev/backend.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.83.1" + } + + tls = { + source = "hashicorp/tls" + version = "4.0.6" + } + } + + backend "s3" { + bucket = "devopslite-tf-state" + key = "dev/terraform.tfstate" + region = "us-east-1" + encrypt = true + dynamodb_table = "devopslite-tf-state" + } +} diff --git a/env/dev/main.tf b/env/dev/main.tf new file mode 100644 index 0000000..0d91403 --- /dev/null +++ b/env/dev/main.tf @@ -0,0 +1,120 @@ +provider "aws" { + region = var.aws_region +} + +module "vpc" { + source = "../../modules/vpc" + aws_region = var.aws_region + default_tags = var.default_tags + project = var.project + environment = var.environment + private_subnets_cidr = var.private_subnets_cidr + public_subnets_cidr = var.public_subnets_cidr + vpc_cidr = var.vpc_cidr +} + +module "vpc_endpoint" { + source = "../../modules/vpc-endpoint" + aws_region = var.aws_region + default_tags = var.default_tags + project = var.project + environment = var.environment + private_subnets = module.vpc.aws_subnets_private + route_table_ids = [module.vpc.private_route_table] + vpc_cidr = [module.vpc.cidr_block] + vpc_id = module.vpc.vpc_id + + depends_on = [module.vpc] +} + +module "kms" { + source = "../../modules/kms" + default_tags = var.default_tags + project = var.project + environment = var.environment +} + +module "ssm" { + source = "../../modules/ssm" + project = var.project + environment = var.environment +} + +module "bastion" { + source = "../../modules/bastion" + default_tags = var.default_tags + project = var.project + environment = var.environment + ami_id = var.bastion_ami_id + bastion_instance_profile_name = module.ssm.ssm_instance_profile_name + instance_type = var.bation_instance_type + private_subnet_id = module.vpc.aws_subnets_private[0] + vpc_cidr = [module.vpc.cidr_block] + vpc_id = module.vpc.vpc_id + + depends_on = [ + module.vpc, + module.ssm + ] +} + +module "ecr_fe" { + source = "../../modules/ecr" + default_tags = var.default_tags + project = var.project + environment = var.environment + kms_key_arn = module.kms.kms_arn + repository_name = "devopslite-fe" + + depends_on = [module.kms] +} + +module "ecr_be" { + source = "../../modules/ecr" + default_tags = var.default_tags + project = var.project + environment = var.environment + kms_key_arn = module.kms.kms_arn + repository_name = "devopslite-be" + + depends_on = [module.kms] +} + +module "eks" { + source = "../../modules/eks" + default_tags = var.default_tags + project = var.project + environment = var.environment + bastion_sg_id = module.bastion.bastion_sg_id + vpc_id = module.vpc.vpc_id + vpc_cidr = [module.vpc.cidr_block] + private_subnets = module.vpc.aws_subnets_private + eks_cluster_version = var.eks_cluster_version + kms_key_arn = module.kms.kms_arn + custom_ami_id = var.custom_ami_id + node_group_name = var.node_group_name + node_capacity_type = var.node_capacity_type + node_instance_type = var.node_instance_type + node_group_desired_capacity = var.node_group_desired_capacity + node_group_min_size = var.node_group_min_size + node_group_max_size = var.node_group_max_size + + depends_on = [ + module.vpc, + module.kms, + module.bastion + ] +} + +module "eks_access" { + source = "../../modules/eks-access" + project = var.project + environment = var.environment + access_entry_type = var.access_entry_type + access_scope_type = var.access_scope_type + kubernetes_groups = var.kubernetes_groups + policy_arn = var.policy_arn + principal_arn = var.principal_arn + + depends_on = [module.eks] +} diff --git a/env/dev/outputs.tf b/env/dev/outputs.tf new file mode 100644 index 0000000..ee2ca63 --- /dev/null +++ b/env/dev/outputs.tf @@ -0,0 +1,34 @@ +output "eks_cluster_endpoint" { + description = "The endpoint for the EKS cluster." + value = module.eks.eks_cluster_endpoint +} + +output "eks_cluster_id" { + description = "The ID of the EKS cluster." + value = module.eks.eks_cluster_id +} + +output "eks_cluster_oidc_issuer_url" { + description = "The OIDC issuer URL for the EKS cluster." + value = module.eks.eks_cluster_oidc_issuer_url +} + +output "eks_cluster_security_group_id" { + description = "The security group ID for the EKS cluster." + value = module.eks.eks_cluster_security_group_id +} + +output "eks_cluster_serviceaccount_role_arn" { + description = "The ARN of the IAM role used by service accounts in the EKS cluster." + value = module.eks.eks_cluster_serviceaccount_role_arn +} + +output "eks_node_group_arn" { + description = "The ARN of the EKS node group." + value = module.eks.eks_node_group_arn +} + +output "eks_node_group_role_arn" { + description = "The ARN of the IAM role used by the EKS node group." + value = module.eks.eks_node_group_role_arn +} diff --git a/env/dev/variables.tf b/env/dev/variables.tf new file mode 100644 index 0000000..ebb1cf8 --- /dev/null +++ b/env/dev/variables.tf @@ -0,0 +1,131 @@ +variable "access_entry_type" { + description = "Type of access entry (STANDARD, EC2, EC2_LINUX, EC2_WINDOWS, FARGATE_LINUX)" + type = string + default = "STANDARD" +} + +variable "access_scope_type" { + description = "Type of access scope (namespace or cluster)" + type = string + default = "cluster" +} + +variable "aws_region" { + type = string + default = "us-east-1" +} + +variable "bastion_ami_id" { + description = "AMI ID for Bastion Host" + type = string + default = "ami-05576a079321f21f8" # Amazon Linux 2023 AMI +} + +variable "bation_instance_type" { + description = "Instance type for bastion host" + type = string + default = "t3.micro" +} + +variable "custom_ami_id" { + description = "Custom AMI ID for EKS nodes" + type = string + default = "ami-0e28a3d4672edb444" # Get ID after Packer builds AMI +} + +variable "default_tags" { + type = map(string) + default = { + Environment = "dev" + Provisioner = "terraform" + Project = "devopslite" + } +} + +variable "eks_cluster_version" { + description = "Kubernetes version for the EKS cluster" + type = string + default = "1.31" +} + +variable "environment" { + type = string + default = "dev" +} + +variable "kubernetes_groups" { + description = "List of Kubernetes groups to grant access to the EKS cluster" + type = list(string) + default = ["admin"] +} + +variable "node_capacity_type" { + description = "Capacity type for the EKS node group (ON_DEMAND or SPOT)" + type = string + default = "ON_DEMAND" +} + +variable "node_group_desired_capacity" { + description = "Desired number of nodes in the EKS node group" + type = number + default = 2 +} + +variable "node_group_max_size" { + description = "Maximum number of nodes in the EKS node group" + type = number + default = 3 +} + +variable "node_group_min_size" { + description = "Minimum number of nodes in the EKS node group" + type = number + default = 1 +} + +variable "node_group_name" { + description = "Name of the EKS node group" + type = string + default = "ng" +} + +variable "node_instance_type" { + description = "Instance type for the EKS nodes" + type = string + default = "t3.small" +} + +variable "policy_arn" { + description = "ARN of the IAM policy to associate with the principal" + type = string + default = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" +} + +variable "principal_arn" { + description = "ARN of the principal to grant access to the EKS cluster" + type = string + default = null +} + +variable "private_subnets_cidr" { + description = "CIDR blocks for the private subnets" + type = list(string) + default = ["172.16.10.0/24", "172.16.20.0/24", "172.16.30.0/24"] +} + +variable "project" { + type = string + default = "devopslite" +} + +variable "public_subnets_cidr" { + description = "CIDR blocks for the public subnets" + type = list(string) + default = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"] +} + +variable "vpc_cidr" { + description = "CIDR block for the VPC" + type = string + default = "172.16.0.0/16" +} diff --git a/modules/bastion/main.tf b/modules/bastion/main.tf new file mode 100644 index 0000000..c48741b --- /dev/null +++ b/modules/bastion/main.tf @@ -0,0 +1,60 @@ +data "aws_region" "current" {} + +resource "aws_security_group" "bastion_sg" { + name = "${var.project}-${var.environment}-bastion-sg" + vpc_id = var.vpc_id + description = "Security group for bastion host" + ingress { + description = "Allow SSH from VPC CIDR" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = var.vpc_cidr + } + egress { + description = "Allow all traffic to all destinations" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["0.0.0.0/0"] + } + tags = merge( + var.default_tags, + { + Name = "${var.project}-${var.environment}-bastion-sg" + } + ) +} + +resource "aws_instance" "bastion_host" { + # checkov:skip=CKV_AWS_126: "Ensure that detailed monitoring is enabled for EC2 instances" + ami = var.ami_id + instance_type = var.instance_type + subnet_id = var.private_subnet_id + vpc_security_group_ids = [aws_security_group.bastion_sg.id] + iam_instance_profile = var.bastion_instance_profile_name + ebs_optimized = true + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + } + + root_block_device { + encrypted = true + } + + tags = merge( + var.default_tags, + { + Name = "${var.project}-${var.environment}-bastion-host" + } + ) + user_data = base64encode(<<-EOF + #!/bin/bash + yum install -y https://s3.${data.aws_region.current.name}.amazonaws.com/amazon-ssm-${data.aws_region.current.name}/latest/linux_amd64/amazon-ssm-agent.rpm + systemctl enable amazon-ssm-agent + systemctl start amazon-ssm-agent + EOF + ) +} diff --git a/modules/bastion/outputs.tf b/modules/bastion/outputs.tf new file mode 100644 index 0000000..9fc848e --- /dev/null +++ b/modules/bastion/outputs.tf @@ -0,0 +1,9 @@ +output "bastion_instance_id" { + value = aws_instance.bastion_host.id + description = "The ID of bastion ec2 instance" +} + +output "bastion_sg_id" { + value = aws_security_group.bastion_sg.id + description = "The ID of bastion security group" +} diff --git a/modules/bastion/variables.tf b/modules/bastion/variables.tf new file mode 100644 index 0000000..93c5bd1 --- /dev/null +++ b/modules/bastion/variables.tf @@ -0,0 +1,45 @@ +variable "ami_id" { + type = string + description = "AMI ID for Bastion Host" +} + +variable "bastion_instance_profile_name" { + type = string + description = "Name of bastion IAM Instance Profile" +} + +variable "default_tags" { + type = map(string) + default = {} + description = "The default tags to apply to resources" +} + +variable "environment" { + type = string + description = "Environment name" +} + +variable "instance_type" { + type = string + description = "Instance type for bastion host" +} + +variable "private_subnet_id" { + type = string + description = "ID of the private subnet" +} + +variable "project" { + type = string + description = "Project name" +} + +variable "vpc_cidr" { + type = list(string) + description = "VPC CIDR" +} + +variable "vpc_id" { + type = string + description = "ID of the VPC" +} diff --git a/modules/ecr/main.tf b/modules/ecr/main.tf new file mode 100644 index 0000000..8985d1e --- /dev/null +++ b/modules/ecr/main.tf @@ -0,0 +1,69 @@ +resource "aws_ecr_repository" "repository" { + name = var.repository_name + image_tag_mutability = "IMMUTABLE" + force_delete = true + + image_scanning_configuration { + scan_on_push = true + } + + encryption_configuration { + encryption_type = "KMS" + kms_key = var.kms_key_arn + } + + tags = merge( + var.default_tags, + { + Name = "${var.project}-${var.environment}-${var.repository_name}" + } + ) +} + +resource "aws_ecr_lifecycle_policy" "policy_untagged" { + repository = aws_ecr_repository.repository.name + + policy = <