Ryan DeBerry – SMS https://www.sms.com Solving | Managing | Securing Thu, 07 Dec 2023 18:25:32 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.3 https://www.sms.com/wp-content/uploads/2023/05/cropped-sms-favicon-32x32.png Ryan DeBerry – SMS https://www.sms.com 32 32 Use Azure Automation Runbook to deploy Nessus Agent via Terraform https://www.sms.com/blog/use-azure-automation-runbook-to-deploy-nessus-agent-via-terraform/ https://www.sms.com/blog/use-azure-automation-runbook-to-deploy-nessus-agent-via-terraform/#respond Thu, 02 Nov 2023 15:56:43 +0000 https://www.sms.com/?p=6981 Problem

All Virtual Machines (VMs) in the Azure environment must have Nessus Agent installed and registered to a newly created Nessus Manager without direct SSH or RDP access to any of the VMs.

Solution

Use an existing Azure Automation Account to deploy the Nessus Agent via a runbook. The runbook will add a Virtual Machine extension that will have the necessary steps to install and register the Nessus Agent based on the Operating System. This solution can be used to install pretty much anything on a Windows or Linux Virtual Machine.

What is an Azure Automation Account?

An Azure Automation Account is a cloud-based management service provided by Microsoft, designed to help automate, orchestrate, and manage repetitive tasks and processes within the Azure environment. It serves as a centralized location for storing various automation assets, such as runbooks, credentials, and integration modules, enabling users to streamline their automation efforts and improve operational efficiency.

In this case, an existing Azure Automation Account that was previously created is being used for this effort. If you don’t have an existing one, you can create a new one strictly for this purpose. There are a couple of requirements that are needed to make this work.

  • Associate a User-assigned Managed Identity with at a minimum, the “Virtual Machine Contributor” Azure role to all subscriptions in your tenant.
  • The Azure Automation Account must be linked to the same Log Analytics workspace that your VMs are linked. In this environment this task was previously taken care of to accomplish another effort. To associate VMs to a Log Analytics workspace, you will need the OMS or the MMA agent. See the link below as there are many ways to tackle this.

As mentioned above, if you don’t already have an Automation Account, you will need to create one. Below is an example of creating an Azure Automation Account with Terraform.

resource "azurerm_automation_account" "aa_account" {
location = "<azure region>"
name     = "<name of account>"
resource_group_name = var.rg
identity {
  identity_ids = ["<Your Managed identity ids>"]
  type         = "UserAssigned
}

What is an Azure Automation Runbook?

An Azure Automation Runbook is a set of tasks or operations that you can automate within the Azure environment. It is essentially a collection of PowerShell or Python script(s) that perform various actions, such as managing resources, configuring systems, or handling other operational tasks. Azure Automation Runbooks are commonly used for automating repetitive tasks, scheduling maintenance activities, and orchestrating complex workflows within Azure.

PowerShell 5.x was the scripting language used for this task, in part because Terraform does not currently support Powershell 7.1 as a runbook type. (e.g. https://github.com/hashicorp/terraform-provider-azurerm/issues/14089).

Terraform

Terraform is the current Infrastructure As Code tool for this environment therefore it used in this scenario. Below is a snippet of the main.tf

Let’s take a look at the Terraform code:

resource "azurerm_automation_runbook" "nessus_install" {
  name                    = var.runbook_name
  location                = data.azurerm_resource_group.ops.location
  resource_group_name     = data.azurerm_automation_account.ops.resource_group_name
  automation_account_name = data.azurerm_automation_account.ops.name
  log_verbose             = true
  log_progress            = true
  description             = var.runbook_description
  runbook_type            = var.runbook_type
  tags                    = var.default_tags
  content = templatefile("${path.module}/runbook/nessus.ps1", {
    umi                         = data.azurerm_user_assigned_identity.identity.client_id
    tenantid                    = var.tenant_id
    scriptnamelinux             = var.scritpname_linux
    scriptnamewindows           = var.scritpname_win
    storageaccountcontainer     = data.azurerm_storage_container.sa.name
    storageaccountresourcegroup = data.azurerm_resource_group.sa.name
    storageaccountname          = var.sa_acct
    workbookname                = var.runbook_name
    storageaccountsub           = data.azurerm_subscription.sa.subscription_id
    client_id                   = data.azurerm_user_assigned_identity.identity.client_id
    vms_to_exclude              = join(",", [for vm in local.vms_file_content : "\"${vm}\""])
    defaultsub                  = ""
  })
}

resource "azurerm_automation_job_schedule" "nessus_install" {
  resource_group_name     = data.azurerm_automation_account.ops.resource_group_name
  automation_account_name = data.azurerm_automation_account.ops.name
  schedule_name           = azurerm_automation_schedule.nessus_install.name
  runbook_name            = azurerm_automation_runbook.nessus_install.name

}

resource "azurerm_automation_schedule" "nessus_install" {
  name                    = var.nessus_schedule
  resource_group_name     = data.azurerm_automation_account.ops.resource_group_name
  automation_account_name = data.azurerm_automation_account.ops.name
  frequency               = var.schedule_frequency
  timezone                = var.timezone
  start_time              = var.start_time
  description             = var.schedule_description
  week_days               = var.week_days
  expiry_time             = var.expiry_time
}

azurerm_automation_runbook:  This section defines the Azure Automation Runbook, including its name, location, resource group, and related configurations. The templatefile is using several inputs that allow you to modify your variables and have your script configured with the desired output. The script utilizes a PowerShell script file named `nessus.ps1`, which is responsible for orchestrating the Nessus installation process and covered in the next section.

azurerm_automation_job_schedule: Here, we set up an Azure Automation Job Schedule, which determines the frequency and timing of the execution of the Nessus installation process.

azurerm_automation_schedule: This section specifies the details of the schedule, including the frequency, time zone, start time, and expiry time for the Nessus installation process. This needs to be run on a weekly basis to incorporate any new VMs that get created in any subscription.

If you choose to use the code as-is, the variables used in the templatefile are explained below.

    umi  = User Managed Identity that is associated with the Azure Automation Account
    tenantid                    = The Tenant ID 
    scriptnamelinux             = Name of Linux shell script
    scriptnamewindows           = Name of Windows script
    storageaccountcontainer     = Name of the Storage Account where the scripts reside
    storageaccountresourcegroup = Name of the Resource Group where the Storage Account resides
    storageaccountname          = Name of the Storage Account
    workbookname                = Name of the Runbook you are creating
    storageaccountsub           = The Subscription ID of the Storage Account
    vms_to_exclude              = join(",", [for vm in local.vms_file_content : "\"${vm}\""])
    defaultsub                  = "" # If you want to loop through all active subscriptions leave this as-is, if not put in the subscription you want to this script to run against

vms_to_exclude variable was configured so you can skip VMs by name if you choose. An issue occurred where a VM’s resources were pegged and the script would eventually error out waiting for the VM to finish. So this logic was inserted to mitigate that. A flat txt file “vms.txt” is used for this purpose, you can just list all VMs in this file, one per line.

Powershell

Let’s take a look at the Powershell script that is being called nessus.ps1

Disable-AzContextAutosave -Scope Process

$AzureContext = (Connect-AzAccount -Identity -Environment AzureUSGovernment -AccountId ${umi}).context
$TenantId = '${tenantid}'
$scriptNameLinux = '${scriptnamelinux}'
$scriptNameWindows = '${scriptnamewindows}'
$storageAccountContainer = '${storageaccountcontainer}'
$storageAccountResourceGroup = '${storageaccountresourcegroup}'
$storageAccountName = '${storageaccountname}'
$defaultSubscriptionId = '${defaultsub}'

$settingsLinux = @{
    "fileUris"         = @("https://$storageAccountName.blob.core.usgovcloudapi.net/$storageAccountContainer/$scriptNameLinux")
    "commandToExecute" = "bash $scriptNameLinux"
} | ConvertTo-Json

$settingsWindows = @{
    "fileUris"         = @("https://$storageAccountName.blob.core.usgovcloudapi.net/$storageAccountContainer/$scriptNameWindows")
    "commandToExecute" = "powershell -NonInteractive -ExecutionPolicy Unrestricted -File $scriptNameWindows"
} | ConvertTo-Json

$storageKey = (Get-AzStorageAccountKey -Name $storageAccountName -ResourceGroupName $storageAccountResourceGroup)[0].Value

$protectedSettingsLinux = @{
    "storageAccountName" = $storageAccountName
    "storageAccountKey"  = $storageKey
} | ConvertTo-Json

$protectedSettingsWindows = @{
    "storageAccountName" = $storageAccountName
    "storageAccountKey"  = $storageKey
} | ConvertTo-Json

$currentAZContext = Get-AzContext

if ($currentAZContext.Tenant.id -ne $TenantId) {
    Write-Output "This script is not authenticated to the needed tenant. Running authentication."
    Connect-AzAccount -TenantId $TenantId
}
else {
    Write-Output "This script is already authenticated to the needed tenant - reusing authentication."
}

$subs = @()

if ($defaultSubscriptionId -eq "") {
    $subs = Get-AzSubscription -TenantId $TenantId | Where-Object { $_.State -eq "Enabled" }
}
else {
    if ($defaultSubscriptionId.IndexOf(',') -eq -1) {
        $subs = Get-AzSubscription -TenantId $TenantId -SubscriptionId $defaultSubscriptionId
    }
    else {
        $defaultSubscriptionId = $defaultSubscriptionId -replace '\s', ''
        $subsArray = $defaultSubscriptionId -split ","
        foreach ($subsArrayElement in $subsArray) {
            $currTempSub = Get-AzSubscription -TenantId $TenantId -SubscriptionId $subsArrayElement
            $subs += $currTempSub
        }
    }
}



$excludeVmNamesArray = (${vms_to_exclude})


foreach ($currSub in $subs) {
    Set-AzContext -subscriptionId $currSub.id -Tenant $TenantId

    if (!$?) {
        Write-Output "Error occurred during Set-AzContext. Error message: $( $error[0].Exception.InnerException.Message )"
        Write-Output "Trying to disconnect and reconnect."
        Disconnect-AzAccount
        Connect-AzAccount -TenantId $TenantId -SubscriptionId $currSub.id
        Set-AzContext -subscriptionId $currSub.id -Tenant $TenantId
    }

    $VMs = Get-AzVM

    foreach ($vm in $VMs) {
        if ($excludeVmNamesArray -contains $vm.Name) {
            Write-Output "Skipping VM $($vm.Name) as it is excluded."
            continue
        }

        $status = (Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status).Statuses[1].DisplayStatus

        if ($status -eq "VM running") {
            Write-Output "Processing running VM $( $vm.Name )"

            $extensions = (Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name).Extensions

            foreach ($ext in $extensions) {
                if ($null -ne $vm.OSProfile.WindowsConfiguration) {
                    if ($ext.VirtualMachineExtensionType -eq "CustomScriptExtension") {
                        Write-Output "Removing CustomScriptExtension with name $( $ext.Name ) from VM $( $vm.Name )"
                        Remove-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -VMName $vm.Name -Name $ext.Name -Force
                        Write-Output "Removed CustomScriptExtension with name $( $ext.Name ) from VM $( $vm.Name )"
                    }
                }
                else {
                    if ($ext.VirtualMachineExtensionType -eq "CustomScript") {
                        Write-Output "Removing CustomScript extension with name $( $ext.Name ) from VM $( $vm.Name )"
                        Remove-AzVMExtension -ResourceGroupName $vm.ResourceGroupName -VMName $vm.Name -Name $ext.Name -Force
                        Write-Output "Removed CustomScript extension with name $( $ext.Name ) from VM $( $vm.Name )"
                    }
                }
            }

            if ($vm.StorageProfile.OsDisk.OsType -eq "Windows") {
                Write-Output "Windows VM detected: $( $vm.Name )"
                $settingsOS = $settingsWindows
                $protectedSettingsOS = $protectedSettingsWindows
                $publisher = "Microsoft.Compute"
                $extensionType = "CustomScriptExtension"
                $typeHandlerVersion = "1.10"
            }
            elseif ($vm.StorageProfile.OsDisk.OsType -eq "Linux") {
                Write-Output "Linux VM detected: $( $vm.Name )"
                $settingsOS = $settingsLinux
                $protectedSettingsOS = $protectedSettingsLinux
                $publisher = "Microsoft.Azure.Extensions"
                $extensionType = "CustomScript"
                $typeHandlerVersion = "2.1"
            }
            $customScriptExtensionName = "NessusInstall"

            Write-Output "$customScriptExtensionName installation on VM $( $vm.Name )"

            Set-AzVMExtension -ResourceGroupName $vm.ResourceGroupName `
                -Location $vm.Location `
                -VMName $vm.Name `
                -Name $customScriptExtensionName `
                -Publisher $publisher `
                -ExtensionType $extensionType `
                -TypeHandlerVersion $typeHandlerVersion `
                -SettingString $settingsOS `
                -ProtectedSettingString $protectedSettingsOS

            Write-Output "---------------------------"
        }
        else {
            Write-Output "VM $( $vm.Name ) is not running, skipping..."
        }
    }

    Set-AzContext -SubscriptionId $defaultSubscriptionId -Tenant $TenantId
}

This particular environment is in the AzureGOV region but could be modified to use any region.

The script is designed to automate the deployment of custom scripts/extensions to multiple Azure VMs across different subscriptions. It provides flexibility for both Linux and Windows VMs and ensures that any existing custom script extensions are removed before deployment; this is because you cannot have an extension with the same name.

OS Scripts

Now lets look at the windows script that the “nessus.ps1” calls

$installerUrl = "<URL to the msi>"


$NESSUS_GROUP="<Name of your Nessus Group>"

$NESSUS_KEY="<Name of Nessus Key>"

$NESSUS_SERVER="<FQDN of Nessus Server>"

$NESSUS_PORT="<Port if different from standard 8834>"

$installerPath = "C:\TEMP\nessusagent.msi"

$windows_package_name = "'Nessus Agent (x64)'"

$installed = Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name = $windows_package_name" | Select-Object Name

function Test-Admin {

    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())

    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)

}

 

if ((Test-Admin) -eq $false) {

    if ($elevated) {

    }

    else {

        Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))

    }

    exit

}

 
'running with full privileges'

if ($installed) {

    Write-Output "Nessus Agent is already installed. Exiting."

}

else {

    Write-Output "Downloading Nessus Agent MSI installer..."

    Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath


    Write-Output "Installing Nessus Agent..."

    Start-Process -FilePath msiexec.exe -ArgumentList '/i C:\TEMP\nessusagent.msi NESSUS_GROUPS="$NESSUS_GROUP" NESSUS_SERVER="$NESSUS_SERVER" NESSUS_KEY='$NESSUS_KEY' /qn' -Wait

    $installed = Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name = $windows_package_name" | Select-Object Name


    if ($installed) {

        Write-Output "Nessus Agent has been successfully installed."

    }

    else {

        Write-Output "Failed to install Nessus Agent."

    }

}

 
if (Test-Path $installerPath) {

    Remove-Item -Path $installerPath -Force

}

 
Function Start-ProcessGetStream {


    [CmdLetBinding()]

    Param(

        [System.IO.FileInfo]$FilePath,

        [string[]]$ArgumentList

    )


    $pInfo = New-Object System.Diagnostics.ProcessStartInfo

    $pInfo.FileName = $FilePath

    $pInfo.Arguments = $ArgumentList

    $pInfo.RedirectStandardError = $true

    $pInfo.RedirectStandardOutput = $true

    $pinfo.UseShellExecute = $false

    $pInfo.CreateNoWindow = $true

    $pInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden


    $proc = New-Object System.Diagnostics.Process

    $proc.StartInfo = $pInfo


    Write-Verbose "Starting $FilePath"

    $proc.Start() | Out-Null

    Write-Verbose "Waiting for $($FilePath.BaseName) to complete"

    $proc.WaitForExit()

    $stdOut = $proc.StandardOutput.ReadToEnd()

    $stdErr = $proc.StandardError.ReadToEnd()

    $exitCode = $proc.ExitCode

 

    Write-Verbose "Standard Output: $stdOut"

    Write-Verbose "Standard Error: $stdErr"

    Write-Verbose "Exit Code: $exitCode"


    [PSCustomObject]@{

        "StdOut"   = $stdOut

        "Stderr"   = $stdErr

        "ExitCode" = $exitCode

    }

}



Function Get-NessusStatsFromStdOut {

 

    Param(

        [string]$stdOut

    )

 

    $stats = @{}

 

 

 

    $StdOut -split "`r`n" | ForEach-Object {

        if ($_ -like "*:*") {

            $result = $_ -split ":"

            $stats.add(($result[0].Trim() -replace "[^A-Za-z0-9]", "_").ToLower(), $result[1].Trim())

        }

    }


    Return $stats

}


Function Get-DateFromEpochSecond {

    Param(

        [int]$seconds

    )

 

    $utcTime = (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($seconds))

    Return Get-Date $utcTime.ToLocalTime() -Format "yyyy-MM-dd HH:mm:ss"

}

 
Try {

    $nessusExe = Join-Path $env:ProgramFiles -ChildPath "Tenable\Nessus Agent\nessuscli.exe" -ErrorAction Continue

}

Catch {

    Throw "Cannot find NessusCli.exe, installing..."

}

 

Write-Output "Getting Agent Status..."

$agentStatus = Start-ProcessGetStreams -FilePath $nessusExe -ArgumentList "agent status"

 

If ($agentStatus.stdOut -eq "" -and $agentStatus.StdErr -eq "") {

    Throw "No Data Returned from NessusCli, linking now"

    Start-ProcessGetStreams -FilePath $nessusExe -ArgumentList 'agent link --key=$NESSUS_KEY --groups="$NESSUS_GROUP" --host=$NESSUS_SERVER --port=$NESSUS_PORT'

}

elseif ($agentStatus.StdOut -eq "" -and $agentStatus.StdErr -ne "") {

    Throw "StdErr: $($agentStatus.StdErr)"

}

elseif (-not($agentStatus.stdOut -like "*Running: *")) {

    Throw "StdOut: $($agentStatus.StdOut)"

}

else {

    $stats = Get-NessusStatsFromStdOut -stdOut $agentStatus.StdOut

    If ($stats.linked_to -eq '$NESSUS_SERVER' -and $stats.link_status -ne 'Not linked to a manager') {

        Write-Output "Connected to $NESSUS_SERVER"

    }

    else {

        Write-Output "Connecting..."

        Start-ProcessGetStreams -FilePath "C:\Program Files\Tenable\Nessus Agent\nessuscli.exe" -ArgumentList 'agent link --key=$NESSUS_KEY --groups="$NESSUS_GROUP" --host=$NESSUS_SERVER --port=$NESSUS_PORT'

    }


    If ($stats.last_connection_attempt -as [int]) { $stats.last_connection_attempt = Get-DateFromEpochSeconds $stats.last_connection_attempt }

    If ($stats.last_connect -as [int]) { $stats.last_connect = Get-DateFromEpochSeconds $stats.last_connect }

    If ($stats.last_scanned -as [int]) { $stats.last_connect = Get-DateFromEpochSeconds $stats.last_scanned }

}

 
#$stats | Out-Host

This script streamlines the process of installing and linking the Nessus Agent to the specified Nessus server, automating various steps and ensuring the seamless deployment and integration of the agent within the intended environment.

Now lets look at the linux script that the “nessus.ps1” calls:

#!/bin/bash

exec 3>&1 4>&2

trap 'exec 2>&4 1>&3' 0 1 2 3

exec 1>/tmp/nessus-install-log.out 2>&1

 

PACKAGE_NAME="nessusagent"

ACTIVATION_CODE="<Your Nessus Activation Key/Code>"

NESSUS_HOST="<fqdn of your Nessus Manager>"

NESSUS_AGENT="/opt/nessus_agent/sbin/nessuscli"

NESSUS_PORT="<port # if different from 8834>"

NESSUS_GROUP="<name of your group>"

base_url="<url to your Storage Account>"

debian_filename="NessusAgent-10.3.1-ubuntu1404_amd64.deb" # 

redhat_7_filename="NessusAgent-10.3.1-es7.x86_64.rpm" # Redhat EL7 filename

redhat_8_filename="NessusAgent-10.3.1-es8.x86_64.rpm" # Redhat EL8 filename

 

 

if_register_agent() {

  if  "$NESSUS_AGENT" agent status | grep -q "Linked to: $NESSUS_HOST"; then

    echo "Nessus Agent is already linked to Nessus Manager."

  else

    $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP"

    if [ $? -eq 0 ]; then

        echo "Nessus Agent linked successfully."

        else

          echo "Failed to link Nessus Agent. Check your activation code or permissions."

          exit 1

    fi

  fi

}

 

is_package_installed_debian() {

  if  dpkg -l | grep -i "ii  $PACKAGE_NAME"; then

    if_register_agent

    return 0

  else

    return 1

  fi

}

 

is_package_installed_redhat() {

  if  rpm -qa | grep -i "$PACKAGE_NAME" > /dev/null; then

    if_register_agent

    return 0

  else

    return 1

  fi

}

 

install_package_debian() {

  echo "$PACKAGE_NAME is not installed on $ID. Installing it now..." &&

  sleep 20 &&

   wget -qP /tmp $base_url$debian_filename &&

  sleep 20 &&

   dpkg -i /tmp/"$debian_filename" &&

  sleep 20 &&

   $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" &&

  sleep 20 &&

   systemctl enable nessusagent --now &&

  sleep 20 &&

   $NESSUS_AGENT agent status |  tee /tmp/nessus_agent_status &&

   sleep 20 &&

   rm -f /tmp/"$debian_filename"

   exit

}

 

 

install_package_redhat_v7() {

  echo "$PACKAGE_NAME is not installed on $ID-$VERSION_ID Installing it now..."

  yum -y install wget &&

  sleep 20 &&

   wget -qP /tmp $base_url$redhat_7_filename &&

  sleep 20 &&

   rpm -ivh /tmp/"$redhat_7_filename" &&

  sleep 20 &&

   $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" &&

  sleep 20 &&

   systemctl enable nessusagent --now &&

  sleep 20 &&

   $NESSUS_AGENT agent status |  tee /tmp/nessus_agent_status &&

   rm -f /tmp/"$redhat_7_filename"

   exit

}

 

install_package_redhat_v8() {

  echo "$PACKAGE_NAME is not installed on $ID-$VERSION_ID. Installing it now..."

  sleep 20 &&

   wget -qP /tmp $base_url$redhat_8_filename &&

  sleep 20 &&

   rpm -ivh /tmp/"$redhat_8_filename" &&

  sleep 20 &&

   $NESSUS_AGENT agent link --host="$NESSUS_HOST" --port="$NESSUS_PORT" --key="$ACTIVATION_CODE" --groups="$NESSUS_GROUP" &&

  sleep 20 &&

   systemctl enable nessusagent --now &&

  sleep 20 &&

   $NESSUS_AGENT agent status |  tee /tmp/nessus_agent_status &&

   rm -f /tmp/"$redhat_8_filename"

   exit

}

 

check_debian_based() {

  lowercase_id=$(echo "$ID" | tr '[:upper:]' '[:lower:]')

  if [[ "$lowercase_id" == *debian* || "$lowercase_id" == *ubuntu* ]]; then

    if is_package_installed_debian; then

      echo "$PACKAGE_NAME is already installed on $ID."

      exit 0

    else

      install_package_debian

    fi

  fi

}

 

check_redhat_based() {

  lowercase_id=$(echo "$ID" | tr '[:upper:]' '[:lower:]')

  if [[ "$lowercase_id" == *centos* || "$lowercase_id" == *rhel* || "$lowercase_id" == *ol* || "$lowercase_id" == *el* ]]; then

    if is_package_installed_redhat; then

      echo "$PACKAGE_NAME is already installed on $ID."

      exit 0

    else

      if [[ "$VERSION_ID" == 7 ]]; then

        echo "Red Hat $ID version 7 detected."

        install_package_redhat_v7

      elif [[ "$VERSION_ID" == 8 ]]; then

        echo "Red Hat $ID version 8 detected."

        install_package_redhat_v8

      else

        echo "Unsupported version: $VERSION_ID"

        exit 1

      fi

    fi

  fi

}

 

if [ -f /etc/os-release ]; then

  . /etc/os-release

  check_debian_based

  check_redhat_based

else

  echo "Unsupported Linux distribution."

  exit 1

fi

This script is pretty much the same as the one above except it is for linux distributions. It will determine the OS type and install the necessary agent package and register the agent to the appropriate Nessus Manager.

Example of the variables.tf

variable "default_tags" {
  description = "A map of tags to add to all resources"
  type        = map(string)
  default = {
  }
}

variable "tenant_id" {
  description = "Azure AD Tenate ID of the Azure subscription"
  type        = string
}

variable "nessus_schedule" {
  description = "Name of the Schedule in Automation Account"
  type        = string
  default     = "nessus-automation-schedule"
}

variable "timezone" {
  description = "Name of the Timezone"
  type        = string
  default     = "America/New_York"
}

variable "schedule_description" {
  description = "Schedule Description"
  type        = string
  default     = "This is schedule to download and install Nessus"
}

variable "week_days" {
  description = "Schedule Description"
  type        = list(string)
  default     = ["Monday", "Wednesday", "Saturday"]
}

variable "scritpname_linux" {
  default     = "nessus-linux.sh"
  description = "Name of Linux script"
  type        = string
}

variable "scritpname_win" {
  default     = "nessus-windows.ps1"
  description = "Name of Windows script"
  type        = string
}

variable "sa_container" {
  description = "Name of the Storage Account Container"
  type        = string
}

variable "sa_rg" {
  description = "Name of the Storage Account Resource Group"
  type        = string
}

variable "sa_sub" {
  description = "Subscription ID where the Storage Account lives"
  type        = string
}


variable "sa_acct" {
  description = "Name of the Storage Account"
  type        = string
}

locals {
  vms_file_content = split("\n", file("${path.module}/vms.txt"))
}

variable "schedule_frequency" {
  description = "Job frequency"
  type        = string
  default     = "Week"
}

variable "runbook_name" {
  description = "Name of the runbook"
  type        = string
  default     = "nessus_agent_install"
}

variable "runbook_type" {
  description = "Name of the language used"
  type        = string
  default     = "PowerShell"
}

variable "runbook_description" {
  description = "Description of the Runbook"
  type        = string
  default     = "This runbook will Download and Install the Nessus Agent"
}

variable "start_time" {
  description = "When to start the runbook schedule"
  type        = string
  default     = "2024-10-07T06:00:15+02:00"
}

variable "expiry_time" {
  description = "When to start the runbook schedule"
  type        = string
  default     = "2027-10-07T06:00:15+02:00"
}

variable "identity_sub" {
  description = "Subscription where MI lives"
  type        = string
}


All the above code can be found at the link below.

https://github.com/rdeberry-sms/nessus_aa_runboook
]]>
https://www.sms.com/blog/use-azure-automation-runbook-to-deploy-nessus-agent-via-terraform/feed/ 0
Cisco Certified Internetwork Expert (CCIE) Recertification https://www.sms.com/blog/cisco-certified-internetwork-expert-ccie-recertification/ https://www.sms.com/blog/cisco-certified-internetwork-expert-ccie-recertification/#respond Fri, 29 Jul 2022 19:26:14 +0000 http://sms-old.local/?p=4219 By Ryan DeBerry, Cloud Solutions Architect, SMS

To keep my Cisco Certified Internetwork Expert (CCIE) in an active state, I must recertify every 3 years. I haven’t touched anything related to helping me recertify the CCIE in a year and half…or so I thought.

In September of 2020, after 15+ years as a Network Engineer, I transitioned to a Cloud Engineer (see the following blogpost where I briefly detailed my journey: Network to Cloud). I heard about the new Continuing Education (CE) credits for Cisco, but had not done any research on it until this year. I just assumed, as usual, I needed to check the CCIE written syllabus, look at the new topics, and start labbing away. So, like anything I dread doing, I Googled it first to see how other people have blogged about it; my actual search was “How do I recert CCIE without taking exam.” Not surprising, I found many links that I won’t bother listing here.

All paths lead to Cisco Continuing Education Program, which is a way for an individual to recertify and learn new technologies in the process. The breakdown of number of CEs to certification level are listed in the table below.

Certification Level & Duration CE Credits
Associate – 3 years 30
Specialist – 3 years 40
Professional – 3 years 80
CCIE – 3 years 120
CCDE – 3 years 120

As you can see above, I needed 120 credits to accomplish my task. The process was actually very easy to follow. Head over to Cisco Digital Learning and login with your CSCO ID or email address. Here you will find many courses that can be used for your CE credit; search for “CE Credits.” All courses in this search that are not greyed out are free to take. If a course is greyed-out, it requires a subscription – more on this later.

Each course (free or paid) contains various chapters of content(videos/pdf) with a quiz at the end of each chapter. The passing score for each quiz is 70%; in addition to each chapter, you must pass the final exam to receive the CE credits. Basically, you can claim 61 credits without a subscription, leaving just 59 credits left to obtain. Those 61 credits are all courses that are SDWAN related, so if you don’t have that skill in your bag then you are in luck. I personally did a lot of SDWAN labs/practice with EVE-NG a couple of years ago, so I was pretty familiar with most of the topics. I was able to breeze through most of the material as it was just a refresher. If that is not the case for you, please take the time to learn by doing the labs embedded in the course. For a free course, the material is pretty good.

My suggestion would be to give yourself 2-3 months ahead of your expiration date to get started because you might not know anything about these topics. Below is a list of the courses and how many credits each one will give you.

Course Name Credits
The SD-WAN Mastery Collection – Getting Started – For Customers 6
Planning and Deploying SD-Access Fundamentals (for Customers) 12
The SD-WAN Mastery Collection – Deploying the Data Plane – For Customers 6
The SD-WAN Mastery Collection – Managing the Application Experience – For Customers 6
CUST-SDA-ISE – Preparing the ISE for SD-Access (for Customers) 4
Cisco DNA Center Fast Start Use Cases 5
Securing Branch Internet and Cloud Access with Cisco SD-WAN 11
The SD-WAN Mastery Collection – Bringing Up the Control Plane Devices – For Customers 2
Getting Started with DNA Center Assurance (A-DNAC-ASSUR) v1.0 4
The SD-WAN Mastery Collection – Deploying the Overlay Topology – For Customers 5
 Total 61

These 10 courses took me a little over a week to complete. I then needed to figure out what other courses I needed to purchase. My co-worker Chris Brooks, told me about this course Cisco Certified DevNet Associate, offered in 1, 3, 6 and 12 month plans; it is worth 48 CE credits, which would bring me to 109. I opted for the 1-month plan for $99, since I didn’t know if I would need this material after I obtained the CE credits required for the recertification. This is a really good course to learn about Software Development and Design, Application Development, Automation and more.

Here is where my experience with my recent role transition to the cloud paid off. I was already doing most of what the topics covered in my daily job or previous experience, so it was very easy to follow and complete. Once completed, I only needed 11 additional credits to reach the required 120…but there isn’t a 11-credit offering. So, my next thought was picking a topic that I know pretty well and just go to the final exam and be done with it. At this point of this journey, it has been almost 2 months of videos, pdfs, and labs for each course, and I was burnt out. I found Introduction to 802.1X Operations for Cisco Security Professionals (802.1X) 2.0. I have done quite a bit of work with 802.1x in my career, so I figured this would be a slam dunk and it was. This course normally costs $200 but there was a discount because Cisco Live was going on, so the course ended up being $160.

Looking at this only from a cost perspective, to take the CCIE written would have been $400. To recertify using CE credits, I spent $360, and picked up useful skills in the process; a win in my book. I recommend picking a few courses that you are unfamiliar with so you can learn the foundation of a new technology. While I ended up with more than 120 credits, I accomplished my goal, and gained new skills along the way.

]]>
https://www.sms.com/blog/cisco-certified-internetwork-expert-ccie-recertification/feed/ 0
Network Engineer to Cloud Engineer https://www.sms.com/blog/network-engineer-to-cloud-engineer/ https://www.sms.com/blog/network-engineer-to-cloud-engineer/#respond Fri, 10 Jun 2022 18:43:13 +0000 http://sms-old.local/?p=3862 By Ryan DeBerry, Cloud Solutions Architect, SMS

Introduction

This is an attempt to give other Network Engineers a brain dump of the things you should learn if you want to make the transition from networking to cloud. I decided to do this because an opportunity was presented to me at my company, SMS. I was hesitant as I was comfortable in my previous position, but it turned out to be a good decision.

The Transition

Getting up to speed on Amazon Web Services or AWS was difficult; just like learning anything new, there are many new terms/bells/whistles/you name it. I learn best by building/breaking/fixing/destroying, so I created my own AWS account to get hands on experience, with the goal of achieving the AWS Certified Solutions Architect – Associate.

I quickly learned that there is a lot to learn, A LOT! This exam is like the CCNA, a lot of topics but it doesn’t go too deep. I followed the exam guide syllabus and searched the internet for examples of creating resources, testing, and tearing it down (staying in the free-tier to avoid being charged!)

Disclaimer: I also was forced to learn Infrastructure as Code(IaC)-Terraform, to be exact- so a lot of my experience was probably not conventional. I mean, I did not ClickOps anything, I learned how to use Terraform and learned AWS at the same time. I do not suggest this method; ClickOps is a good way to learn how something works and usually the exams want you to know how it’s done in the UI. The problem with ClickOps is that in production, you shouldn’t be doing that at all unless absolutely necessary. You’ll soon find out it will be impossible to keep track of what you did in ClickOps and also how to delete what you did, which all cloud providers love because you keep getting charged for the provisioned resource.

I spent many evenings learning what I could from Google. I soon realized that YouTube channels are very handy in learning all things cloud-related (see useful links at the bottom of this post). I ended up creating multiple AWS accounts, running various services between them, and “networking” them together as well. I am not going to go into detail about the various terms and options in the cloud, it will make your brain hurt. Just know that your networking and system design knowledge is pivotal in making all this work.

This is when I figured, “I should take the AWS Networking Exam!” That said, I still haven’t taken that exam but I do have at least 12 hours of Adrian Cantrill’s course video under my belt. If you read the exam guide from the AWS Certified Advanced Networking – Specialty, you will see the same terms from your Network Engineer path. My suggestion for the true Network folks is to study for this exam right after the Solutions Architect or at least treat it as your CCIE equivalent. Personally, I don’t think you need to sit for the exam; I’d just learn all the material as if you were going to. You will be drinking from the firehose no matter which path you end up taking. Big skill gap? Maybe, it depends on you.

A good Engineer skillset is comprised of the following:

  1. An analytical mind
  2. An ability to learn new technologies quickly
  3. Good time management skills
  4. An ability to follow processes
  5. An ability to figure things out when no process exist
  6. When to know you don’t know…and know who to reach out to to get that understanding.

Does this really change for a Cloud Engineer? The short answer is no. These are the skills that I think are the most important, not the actual technology itself. You can learn and be an expert at any technology with these foundational skills. The cloud is still infrastructure that needs to be configured and maintained. You still have to know how systems are built and how they can talk to each other and how you can secure them.

Enough of the fluff, you came here to learn how to easily transition.

I put these baseline skills into three categories, this is obviously not a comprehensive list.

Technical skills you must have (high level foundational):

This is in the order I believe you should learn these:

  1. Linux: This is a non-starter IMHO. I could write an entire blog about it. The more experience the better; most, if not all, resources deployed in the cloud are Linux based. It will help you in the long run if you know your way around and how to deal with it when it breaks. I’d recommend installing Linux and using it, without a UI; it’s important to get comfortable with the command line and package manager depending on your distro. Check out this link for distro-agnostic Essential Commands.
  2. Git: GitLab, GitHub, bitbucket, gitea. There are many platforms but git is still git not get; be familiar with the git cli.
  3. IaC: Terraform, Pulumi, Chef, Puppet, Ansible. If you know any of these, it shouldn’t be too hard to learn any other; these all have free versions.
  4. Integrated Development Environment (IDE): Pycharm, IntelliJ, VScode, etc.
  5. Understanding how to create and manipulate the following file formats; JSON, Yaml.

You have to be able to navigate all of these in any cloud environment.

Technical skills that I believe are past foundational

So, you have mastered the foundational aspects…to get to the other side will be like climbing a mountain, but if you make it, there might be a pot of gold.

  1. Basic understanding of a Programming Language: Python, Java, C++, Golang
  2. Container Orchestration: Kubernetes, Docker, Containerd, Nomad
  3. Public Key Infrastructure: Trust me, it pays to understand this, I still struggle
  4. Continuous Integration/Continuous Deployment (CI/CD): Jenkins, Travis, Argo, GitLab, GitHub actions, the list goes on…
  5. Database – General understanding: Relational vs. NoSQL vs. Object Oriented vs. Key/Value

Way Past Foundational

  1. Aviatrix
  2. Rancher

Bookmark this link!

If you don’t do anything else, bookmark this: DevSecOps Learning

This link has a lot of what I have already talked about and more, a lot more.

Concepts to get in your head

  1. Pets vs. Cattle
  2. Don’t Repeat Yourself

YouTube channels that I subscribe to for various Cloud/DevOps resources

  1. DevOps Toolkit
  2. Just Me and OpenSource
  3. Pablos Spot

Cloud Service Provider Links

Each cloud provider has their way of implementing the same thing. You will need to just get your hands dirty to figure out the differences. Get an account with any provider they all have free tiers, and Git-R-Done!

  1. Azure
  2. AWS
  3. Google

Final Thoughts

If you keep an open mind, it won’t be that bad. You will definitely turn into what we call a “glorified Sys Admin” but at the end of the day, you can never learn enough. That is the gift… and the curse!

Positive
Energy
Activates
Constant
Elevation

]]>
https://www.sms.com/blog/network-engineer-to-cloud-engineer/feed/ 0