Automating Windows Workloads in AWS using Systems Manager and PowerShell DSC with Terragrunt Part1.
Tags: terragrunt, powershell, aws, windows
Published: 2024-04-19 00:12:38

Cloud people!

In this opportunity, I want to share a scenary for automated and cover workloads in windows and runned automations based on powershell dsc with systems manager.

Check github repository for the code
{% embed https://github.com/segoja7/aws-dsc-terragrunt-part1 %}

## Requirements
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)
* [Terragrunt](https://terragrunt.gruntwork.io/docs/getting-started/install/)
* [Terraform](https://www.terraform.io/downloads.html)
* [Powershell v4 or above](https://learn.microsoft.com/es-es/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4&ranMID=43674&ranEAID=PPkX79%2Fc*b0&ranSiteID=PPkX79_c.b0-iR7RzEVWiIKasp61B66_Ew&epi=PPkX79_c.b0-iR7RzEVWiIKasp61B66_Ew&irgwc=1&OCID=AIDcmm549zy227_aff_7795_1243925&tduid=(ir__2ojeqjrl6okfdhiiytspihjx032xdhe3ledy2e1h00)(7795)(1243925)(PPkX79_c.b0-iR7RzEVWiIKasp61B66_Ew)()&irclickid=_2ojeqjrl6okfdhiiytspihjx032xdhe3ledy2e1h00)

## Step 1.

The first step is configuring the network layer, it is using the vpc module of registry, all information about this module in this [link](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest). 

> For effects practices only two subnets are created, one private subnet and one public subnet, this demo not need more.

```
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"

  name = local.name
  cidr = local.vpc_cidr

  azs             = local.azs
  public_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 6, k)]
  private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 6, k + 10)]

  enable_nat_gateway   = true
  create_igw           = true
  enable_dns_hostnames = true
  single_nat_gateway   = true

  manage_default_network_acl    = true
  default_network_acl_tags      = { Name = "${local.name}-default" }
  manage_default_route_table    = true
  default_route_table_tags      = { Name = "${local.name}-default" }
  manage_default_security_group = true
  default_security_group_tags   = { Name = "${local.name}-default" }

  tags = local.tags
}
```
## Step 2.
The second step is related to create security group, in this case the open ports are 3389 and 80, and like step 1 it is using the security group module of registry, all information about this module in this [link](https://registry.terraform.io/modules/terraform-aws-modules/security-group/aws/latest). 

```
module "security_group_bastion" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "5.1.0"

  name        = "windows security group"
  description = "windows security group"
  vpc_id      = var.vpc_id

  ingress_with_cidr_blocks = [
    {
      from_port   = 3389
      to_port     = 3389
      protocol    = "tcp"
      description = "rdp ports"
      cidr_blocks = "172.16.0.0/16"
    },
    {
      from_port   = 3389
      to_port     = 3389
      protocol    = "tcp"
      description = "rdp ports"
      cidr_blocks = "0.0.0.0/0"
    },
    {
      from_port   = 5986
      to_port     = 5986
      protocol    = "tcp"
      description = "winrm"
      cidr_blocks = "0.0.0.0/0"
    },
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      description = "http"
      cidr_blocks = "0.0.0.0/0"
    },
  ]
  egress_with_cidr_blocks = [
    {
      description = "Allowing outbound traffic"
      to_port     = 0
      protocol    = "-1"
      cidr_blocks = "0.0.0.0/0"
      from_port   = 0
    },
  ]
}
```
## Step 3.
This step is to deploy the ec2 instance and use the userdata for to prepare the instance with the necessary. But for this simple scenary all modules are installed by default so not is neccesary install modules in userdata process. If you need other modules, you can find in this [link](https://www.powershellgallery.com/packages?q=DscResource&x=0&y=0) the modules, I hope cover other modules in the future.

###userdata
```
<powershell>

New-NetFirewallRule -DisplayName 'Allow local VPC' -Direction Inbound -LocalAddress 172.16.0.0/16 -LocalPort Any -Action Allow
# Instalar y cargar el proveedor NuGet
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Import-PackageProvider -Name NuGet -Force
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
#DSC module IIS
Install-Module -Name WebAdministrationDsc -RequiredVersion 4.1.0 -Confirm:$false -Force

</powershell>
```
> If you need install by example an module dsc different you need to add in the userdata these installation.
Something like this: 
```
Install-Module -Name ComputerManagementDsc -RequiredVersion 9.0.0
-Force -Confirm:$false
```

Important: As best practice, always specific the module version because variables or functions can be changed between version and your configuration can be affected, additional your instance always need to have installed the module that is using your configuration. 

###Windows Ec2
```
module "ec2-instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "5.6.0"
  name    = "windows-server"

  ami           = "ami-00d990e7e5ece7974" #Microsoft Windows Server 2022 Base
  instance_type = "t3.medium"
  subnet_id  = element(var.public_subnet_ids, 0)
  key_name   = "clustersql2"
  monitoring = true
  vpc_security_group_ids      = [var.security_group_id]
  associate_public_ip_address = true


  create_iam_instance_profile = true
  iam_role_description        = "IAM role for EC2 instance"
  iam_role_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }

  user_data = base64encode("${path.module}/userdata/bastion_user_data.ps1")
  user_data_replace_on_change = true

  tags = local.tags

}
```

### Modules by default with windows.

![DSC Modules by default with windows.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldto0ikyhodcup0k20k9.png)

## Step 4.
In this step it is necessary add the script in some part for add to the instance, for this case the script will be add in the s3 bucket for in the next step the automation of systems manager can be downloaded and execute locally in ec2. it is using the s3module of registry, all information about this module in this [link](https://registry.terraform.io/modules/terraform-aws-modules/s3-bucket/aws/latest/submodules/object?tab=inputs). 

```
module "s3-bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "4.1.2"
  bucket = "s3-script-dsc-${random_string.script_suffix.result}"
  force_destroy = true

}

module "s3-bucket_object" {
  source  = "terraform-aws-modules/s3-bucket/aws//modules/object"
  version = "4.1.2"
  bucket = module.s3-bucket.s3_bucket_id
  key    = "dsc.ps1"
  file_source = "../../scripts/dsc.ps1"

}

resource "random_string" "script_suffix" {
  length  = 6
  special = false
  lower   = true
  upper   = false
}
```
> DSC uses a Pull and Push mode, for this scenario it is using the push mode, with a pull mode this step would not be necessary.

###Review script
This script automated the deploy of a website in IIS, create a folder with a index.html and add new content, additional stoped the default website.
```
configuration website { 

    Import-DscResource -ModuleName WebAdministrationDsc -ModuleVersion 4.1.0 

    Node $env:COMPUTERNAME 

    {
        WindowsFeature IIS 
        {
            Ensure          = 'Present'
            Name            = 'Web-Server'
        }
        WindowsFeature AspNet45
        {
            Ensure          = 'Present'
            Name            = 'Web-Asp-Net45'
        }
        
        WebSite DefaultSite
        {
            Ensure          = 'Present'
            Name            = 'Default Web Site'
            State           = 'Stopped'
            ServerAutoStart = $false
            PhysicalPath    = 'C:\inetpub\wwwroot'
            DependsOn       = '[WindowsFeature]IIS'
        }

        File WebContent
        {
            Ensure          = 'Present'
            DestinationPath = 'C:\segoja7\www\index.html'
            Recurse         = $true
            Type            = 'File'
            Contents        = 'cloud people website using dsc'
            DependsOn       = '[WindowsFeature]AspNet45'
        }
        WebSite NewWebsite
        {
            Ensure          = 'Present'
            Name            = 'segoja7'
            State           = 'Started'
            ServerAutoStart = $true
            PhysicalPath    = 'C:\segoja7\www\'
            DependsOn       = '[File]WebContent'
        }
    }
}

website -OutputPath ".\website"
Start-DscConfiguration -Path ".\website" -Wait -Force -ComputerName $env:COMPUTERNAME
```
> You can use the command, for know the submodule structure
```
Get-DscResource -Name WindowsFeature -Syntax
```

![Get-DscResource -Name WindowsFeature -Syntax](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3nkc8e0g8avf41ibrr6.png)


![S3 dsc script](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2adt07x9dzhxgtpz76e6.png)

With the script in s3, The next step is create automation for run in windows ec2 instance.

## Step 5.
In this step it is created an automation and a document, the document is associated in the automation and the automation is associated with an association for trigger the automations when this is created.

```
resource "aws_ssm_document" "dsc-automation" {
  name            = "dsc-automation"
  document_type   = "Automation"
  document_format = "YAML"

  content = templatefile("${path.module}/documents/automation.yaml", {
    instance_id = var.instance_id
    Assume_role   = aws_iam_role.dsc-automationssm-role.arn
  })
}

resource "aws_ssm_document" "dsc-script" {
  name            = "dsc-script"
  document_type   = "Command"
  document_format = "YAML"

  content = templatefile("${path.module}/documents/dsc-script.yaml", {
    names3bucket = var.s3_bucket_id
  })
}

resource "aws_ssm_association" "dsc-association" {
  name             = aws_ssm_document.dsc-automation.name
  association_name = aws_ssm_document.dsc-automation.name
  #  apply_only_at_cron_interval = true
  parameters = {
    Instance = var.instance_id
  }

}


resource "aws_iam_role" "dsc-automationssm-role" {
  name = "wsfc-automationssm-role"
  assume_role_policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Principal" : {
            "Service" : [
              "ssm.amazonaws.com"
            ]
          },
          "Action" : "sts:AssumeRole",
          "Condition" : {
            "StringEquals" : {
              "aws:SourceAccount" : "${data.aws_caller_identity.current.account_id}"
            },
            "ArnLike" : {
              "aws:SourceArn" : "arn:aws:ssm:*:${data.aws_caller_identity.current.account_id}:automation-execution/*"
            }
          }
        }
      ]
    }
  )
}

resource "aws_iam_role_policy_attachment" "dsc-policy-attachment" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"
  role       = aws_iam_role.dsc-automationssm-role.name
}

```
When the automation is created with terraform, this is executed using the instance ID as target.
![ssm automation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grg55c10sjzurzm8p3oy.png)

## Step 6.
It is time for validate the automation, that bassically executed the document dsc-script the step 5, the document make a download of the script located in s3 bucket, later this is executed from C:\Scripts\dsc.ps1.


![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6u50ozbzmd4z6o3hy3m.png)


## Step 7.
With the script executed it is time for check the site web using the public dns of ec2 instance.

![web site using dsc](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drpsbzfrfa3928ak7aul.png)

Conclusion, DSC is a great tool for obtain a state desired of a machine in windows there are many things for cover about of DSC powershell I hope in other oportunity write other blog related to this tool.

If you have any questions, please leave them in the comments!

Successful!!