(In case you’re wondering, yes! This is a hat tip to the lovely book by Al Sweigart, Automate The Boring Stuff With Python)

In part one, I set up a harness for automating recon activities of a pentest using a container, Terraform and Ansible. Part two focuses on automating scans. For scanning, I’ll spin up a VM on my hypervisor and get it to automatically scan an IP address range.

Sample code

As a reminder, all code related to this post is published here: https://github.com/Inf0Junki3/automate-pentest-example.

Spinning up a VM

As with my past post, I have two folders in my project - one for terraform, and one for ansible:

+- terraform
|  |
|  +- main.tf
+- ansible
|  |
|  +- ansible.cfg
|  |
|  +- site.yml
+- config.yml

The terraform piece part takes care of spinning up the VM in my proxmox lab environment. And ansible takes care of installing the packages and running the scan.

# Set up some variables. We'll specify non-confidential information in a config.yml file.
variable "config_file_path" {
  type = string

locals {
    configuration = yamldecode(file(var.config_file_path))

# These are confidential variables. You can set them up in GitHub secrets, Jenkins credentials, etc
variable "proxmox_url" {
  type = string
  sensitive = true

variable "proxmox_username" {
  type = string
  sensitive = true

variable "proxmox_password" {
  type = string
  sensitive = true

variable "proxmox_node" {
  type = string
  sensitive = true

variable "proxmox_base_image" {
  type = string
  sensitive = true

terraform {
    required_providers {
        proxmox = {
            source = "telmate/proxmox"
            version = "2.9.11"

# This sets up the proxmox provider.
provider "proxmox" {
  pm_api_url = var.proxmox_url
  pm_user = var.proxmox_username
  pm_password = var.proxmox_password
  pm_tls_insecure = local.configuration.terraform.proxmox_use_insecure

# This allows you to span the assessment machine in your instance.
resource "proxmox_vm_qemu" "assessment_machine" {
    name = local.configuration.assessment_name
    target_node = var.proxmox_node
    clone = var.proxmox_base_image
    full_clone = true
    os_type = "cloud-init"

    cores = local.configuration.terraform.vm_cores
    memory = local.configuration.terraform.vm_memory
    agent = local.configuration.terraform.vm_install_guest_agent

    disk {
        size = local.configuration.terraform.vm_disk_size
        storage = "local-lvm"
        type = "scsi"

    sshkeys = local.configuration.terraform.ssh_keys

# Now, we write the resulting IPv4 address to the inventory.
resource "local_file" "ansible_inventory" {
    content = templatefile("inventory.tmpl", {
        machine_name = local.configuration.assessment_name
        machine_ip = proxmox_vm_qemu.assessment_machine.default_ipv4_address
        machine_user = local.configuration.terraform.vm_user
    filename = "../ansible/inventory.yml"

Now that you have the main.tf file written to your terraform directory, you can navigate to that directory and run it:

cd terraform
terraform init
terraform apply -state=acme.state
terraform apply
  Enter a value: ../config.yml

  Enter a value: ubuntu-fossa-with-agent

  Enter a value: some-node

  Enter a value: some-password

  Enter a value: https://some-node:8006/api2/json

  Enter a value: root@pam
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

proxmox_vm_qemu.assessment_machine: Creating...
proxmox_vm_qemu.assessment_machine: Creation complete after 1m26s [id=some-node/qemu/111]
local_file.ansible_inventory: Creating...
local_file.ansible_inventory: Creation complete after 0s [id=b6ae1722d1c6fe9cf71f6c1b5fd874de72b29eb2]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

When successful, this spins up a VM and builds your inventory file for you.

Setting yourself up for automated scanning

Our config file from last time is fairly similar but now has some extra non-confidential parameters for proxmox:

  proxmox_use_insecure: true
  vm_cores: 1
  vm_memory: 1024
  vm_install_guest_agent: 1
  vm_user: ubuntu
  vm_disk_size: 20G
  ssh_keys: ssh-rsa mypubkeyhere
scan_type: passive
scan_timeout_seconds: 7200
assessment_name: acme
  - idontexist.home.arpa

Then, I execute the commands in my VM with ansible:

- name: Run scanning from a container
  hosts: all
  - name: Grab variables from the config file
      file: ../config.yml
  - name: Install nmap
      update_cache: true
        - nmap
  - name: Run port scan
      cmd: "nmap -A -oA /tmp/results {{ ip_ranges | join(' ') }}"
    register: nmap_scan
    changed_when: nmap_scan.rc == 0
  - name: Grab the results.xml file
      src: /tmp/results.xml
      dest: /tmp/scan_results

To run this, I navigate to my ansible folder:

cd ../ansible
pipenv shell
# This command need only be run once:
pipenv install
ansible-playbook site.yml

Once this is run, I can access my scan results on my local machine in /tmp/scan_results.

Next post, I’ll explain how this approach can help scale up the repetitive parts of a pentest. We’ll see how these scripts can be set up as repo templates and how they can be integrated with a CI/CD system to run automatically.