Implementing Security Compliance as Code in Terraform

Infrastructure as Code (IaC) tools like Terraform have enabled efficient, accountable and rapid infrastructure development and deployment in the cloud. Without the overhead of delivering, installing and maintaining hardware, the speed at which teams can build and release IT solutions brings measurable value to their organisation.

Working in the security industry, we need to keep up with this rapid deployment methodology, and insert ourselves into the development pipeline to ensure architects/developers are releasing infrastructure that meets our best practices.

This blog post details one way that we as security practitioners can automate compliance with technical security policy as code in Terraform. This method utilises a PowerShell script I wrote called TFCheck.

If you would like to learn more about Terraform, please check out their website.

How does it work?

TFcheck writes the output of the Terraform show command to config.out for parsing. 

When TFcheck parses config.out, it performs string manipulation to convert the config.out into compressed json format. From here TFcheck converts the Json string into Powershell objects using the ConvertFrom-Json function.

All configuration parameters are now nested Powershell objects, which are easy to reference and validate!

Simply write your rules within TFCheck.ps1 and then run it from within your Terraform project directory. Alternatively, you can automate it as a task in various development pipeline tools.

Rules

The world is your oyster. Here are some examples in Azure to get you started.

Internet Facing Services
Do you have certain ports/services you never want exposed to the internet? (e.g. RDP, SSH).
Write a rule to check all network rules in the Terraform config for port 3389, 1433 or 22 permitted from 'Internet' or '*'.

###Network Security Group Checks###
Write-Host "`n***Checking Network Security Group (NSG) rules***"
$NsgErrors = 0
#Loops through Resources and checks security conditions
ForEach ($Name in $Objects.psobject.properties.name) { #Gets Resources
 $ObjectType = ($Objects.$Name | Get-Member -Type NoteProperty).name -Replace "(\[0m)","" #Replace is required to cleanup the first line in the file (which has some prepending characters)
 if ($ObjectType -eq "azurerm_network_security_group"){ #Filters for NSG objects
  $SecurityRuleNumber = try{[int]::parse($Objects.$Name.'security_rule'.'#')}catch{} #Gets the total number of NSG security rules per Resource
  $Rules = 0..$SecurityRuleNumber #Creates an array of zero to the total number of rules per resource, which is used to loop through each rule
  ForEach ($Rule in $Rules){ #Loops through each rule in a resource
   $OutputName = $Objects.$Name.'security_rule'.$Rule.'name'
   #Rule 1 - Internet or * Remote Desktop#
   if ((($Objects.$Name.'security_rule'.$Rule.'source_address_prefix' -eq 'Internet') -or ($Objects.$Name.'security_rule'.$Rule.'source_address_prefix' -eq "`*")) -and ($Objects.$Name.'security_rule'.$Rule.'destination_port_range' -eq '3389') -and ($Objects.$Name.'security_rule'.$Rule.'access' -eq 'allow')){
    Write-Host "[Build Violation] Remote Desktop port 3389 permitted from Internet or * in NSG rule: $OutputName"
    $NsgErrors = 1
    }
   #Rule 2 - Internet or * MSSQL#
   if (($Objects.$Name.'security_rule'.$Rule.'source_address_prefix' -eq 'Internet') -and ($Objects.$Name.'security_rule'.$Rule.'destination_port_range' -eq '1433') -and ($Objects.$Name.'security_rule'.$Rule.'access' -eq 'allow')){
    Write-Host "[Build Violation] MSSQL port 1433 permitted from Internet or * in NSG rule: $OutputName"
    $NsgErrors = 1
    }
   #Rule 3 - Internet or * SSH#
   if (($Objects.$Name.'security_rule'.$Rule.'source_address_prefix' -eq 'Internet') -and ($Objects.$Name.'security_rule'.$Rule.'destination_port_range' -eq '22') -and ($Objects.$Name.'security_rule'.$Rule.'access' -eq 'allow')){
    Write-Host "[Build Violation] SSH port 22 permitted from Internet or * in NSG rule: $OutputName"
    $NsgErrors = 1
    }
   }
  }
 }
if ($NsgErrors -eq 0){
 Write-Host "No errors identified"
 }

Storage Account Encryption
Write a rule to ensure no storage accounts are created without encryption enabled.

#########Encryption Check##########
Write-Host "`n***Checking Storage Account Blob Encryption***"
$EncryptionErrors = 0
#Loops through Resources and checks security conditions
ForEach ($Name in $Objects.psobject.properties.name) { #Gets Resources
  $ObjectType = ($Objects.$Name | Get-Member -Type NoteProperty).name -Replace "(\[0m)","" #Replace is required to cleanup the first line in the file (which has some prepending characters)
  if ($ObjectType -eq "azurerm_storage_account"){ #Filters for Storage Account objects   
  if ($Objects.$Name.'account_kind' -eq "Storage"){
   if(-Not($Objects.$Name.'enable_blob_encryption' -eq "true")){
    Write-Host "[Build Violation] 'enable_blob_encryption' not set to 'true' on storage account $Name"
    $EncryptionErrors = 1
    }
   }
  }
 }
if ($EncryptionErrors -eq 0){
 Write-Host "No errors identified"
 }

Tags
Does your organisation tag assets? You can write a rule to ensure that all resources are tagged appropriately.

############Tag Checking###########
Write-Output "`n***Checking Tags***"
#Loops through Resources and checks security conditions
$RequiredTags = @("responsiblity", "department", "support", "costcenter") #Sets required tag names that are checked against build
$TagErrors = 0
ForEach ($Name in $Objects.psobject.properties.name) { #Gets Resources
 $ObjectType = ($Objects.$Name | Get-Member -Type NoteProperty).name -Replace "(\[0m)","" #Replace is required to cleanup the first line in the file (which has some prepending characters)
 if (-Not(($ObjectType -eq "azurerm_storage_container") -or ($ObjectType -eq "azurerm_subnet") -or ($ObjectType -eq "azurerm_virtual_network_peering"))){ #Excludes non taggable object types
  ForEach ($Tag in $RequiredTags){ #Loops through each rule in a resource
   $TagValue = ([string]($Objects.$Name.'tags'.$Tag)).Trim()
   if ($TagValue.length -lt 4){
    Write-Host "[Build Violation] Missing required tag '$Tag' in object $Name"
    $TagErrors = 1
    }
   }
  }
 }
if ($TagErrors -eq 0){
 Write-Host "No errors identified"
 }

Output

Here is a screenshot of the output presented at run time. The developer will see this during audit, providing instant feedback on the security of their code.


Conclusion

Hopefully you find use in this script and are able to reduce time spent fixing issues which are already deployed or responding to incident that never should have happened.

Please let me know if you run into any issues and I can try to address them in an update. Better yet - fix it yourself on my git :) 

Comments

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. penetration testing training I really loved reading your blog. It was very well authored and easy to understand. Unlike other blogs I have read which are really not that good.Thanks alot!

    ReplyDelete
  3. This is my first time i visit here. I found so many entertaining stuff in your blog, especially its discussion. From the tons of comments on your posts, I guess I am not the only one having all the enjoyment here! Keep up the excellent work.
    how to make french toast

    ReplyDelete
  4. I have found that this site is very informative, interesting and very well written. keep up the nice high quality writing SharePoint Online: Fundamentos

    ReplyDelete
  5. We are really grateful for your blog post. You will find a lot of approaches after visiting your post. We are extremely thankful for your blog entry. Best site to buy YouTube Views

    ReplyDelete
  6. Goօԁ site, nice аnd easy on tһe eyes and excellent сontent as well. best place to buy Instagram followers

    ReplyDelete
  7. Excellent article! We will be linking to this particularly great content on our site. buying YouTube Views

    ReplyDelete

Post a Comment

Popular posts from this blog

Windows PowerShell Remoting: Host Based Investigation and Containment Techniques

LSASS.DMP... Attacker or Admin?

Touch Screen Lexicon Forensics (TextHarvester/WaitList.dat)