Day 002 - Packer integration with 1Password
Introduction
In college, I started tinkering with Debian (Debian GNU/Linux 3.0 - Woody) as my first introduction to other operating systems besides MS-DOS and Windows. The use of deb-packages through apt-get felt so natural that I never got around to using Red Hat or Fedora in a sensible way (yum always felt broken).
I became a heavy user of Ubuntu Server in my homelab, which is still Debian-based, as I wanted to run some more bleeding-edge packages on my servers (at the cost of more maintenance and possible issues). Installing the operating system, however, turned out to be a tedious task if you have to do it over and over again. Creating a template soon became a thing using preseed files, specially with virtualization coming of age in the late 2000s. One of the remaining issues was that I had to keep my template up-to-date or be exposed to (sometimes) lengthy update/upgrade processes.
Packer
Packer makes templating a lot easier because of the involved automation and support for different builders. It also makes it easier to store the configuration of those templates in a Git repository. There is one caveat though: if you are storing the configuration in a version control system, you will likely need the possibility to safely store sensitive data like usernames and passwords.
Enter 1Password CLI.
1Password
Although HashiCorp offers a product to handle sensitive data with Vault, it didn’t make sense for me as I’m already using 1Password. With the recent addition of 1Password CLI, integrating this tool in my workflow is the right thing to do!
Prerequisites
Proxmox
My homelab is based on Proxmox as the hypervisor, which is supported by Packer natively. In my environment, I will use the ISO-builder. For more information on this builder, check https://developer.hashicorp.com/packer/plugins/builders/proxmox/iso.
Everything discussed below should also work for other builders, as the main interface is Packer.
Packer
Make sure Packer is installed according to your platform. Instructions can be found at https://developer.hashicorp.com/packer/downloads.
1Password
Obviously, for this topic you will need an account with 1Password, they have a 14-day trial available.
To get started, you will need to install 1Password CLI and connect to your vault. Use the op --help
command to get a list of all available flags and commands.
Configuration
Packer
Build configuration
My build configuration is referring to a builder-source called homelab-ubuntu-22-04 for the builder proxmox-iso It is also referring to a couple of variables:
- template_script_repository_url
- template_script_repository_name
- ssh_provisioner
After the installation has completed, Packer will run a couple of scripts to customize the installation.
The scripts are located in a public git repository at https://github.com/jantytgat/iac-scripts, and are initiated through the local scripts template.sh
and remove_provisioner_user.sh
.
build {
name = "proxmox-ubuntu-22-04"
sources = ["source.proxmox-iso.homelab-ubuntu-22-04"]
provisioner "shell" {
execute_command = "sudo -S -E bash -c '{{ .Vars }} {{ .Path }}'"
environment_vars = [
"PROVISIONER_SCRIPT_REPOSITORY_URL=${var.template_script_repository_url}",
"PROVISIONER_SCRIPT_REPOSITORY_NAME=${var.template_script_repository_name}",
"PROVISIONER_USERNAME=${var.ssh_provisioner.username}",
]
scripts = [
"../scripts/template.sh",
"../scripts/remove_provisioner_user.sh",
]
}
}
Build source
The build source configuration has all the magic information needed to build an image on Proxmox from an ISO-file. I won’t be covering all the details for this builder, but some fields need some explanation.
source "proxmox-iso" "homelab-ubuntu-22-04" {
# PROXMOX CONNECTION DETAILS
proxmox_url = "${var.proxmox_url}/api2/json"
insecure_skip_tls_verify = var.proxmox_skip_certificate_validation
username = "${var.proxmox_username}@pam"
password = var.proxmox_password
node = lower(var.proxmox_node)
# INSTALLER ISO
iso_file = "${var.proxmox_iso_storage_name}:iso/${var.proxmox_iso_filename}"
iso_checksum = var.proxmox_iso_checksum
unmount_iso = var.proxmox_unmount_iso
# VM DETAILS
vm_id = var.proxmox_vm_id
vm_name = var.proxmox_vm_name
# CLOUD-INIT
cloud_init = true
cloud_init_storage_pool = var.proxmox_vm_storage_name
#http_directory = var.proxmox_cloudinit_path
http_content = {
"/meta-data" = file("cloud-init/meta-data")
"/user-data" = templatefile("cloud-init/user-data", { "provisioner_username" = var.ssh_provisioner.username, "provisioner_password" = bcrypt(var.ssh_provisioner.password, 10), "provisioner_sshkey" = var.ssh_provisioner.sshkey})
}
boot_wait = "5s"
boot_command = [
"c",
"linux /casper/vmlinuz --- autoinstall ds='nocloud-net;s=http://{{.HTTPIP}}:{{.HTTPPort}}/' <enter>",
"initrd /casper/<wait2s>initrd<enter>",
"boot<wait><enter>"
]
# VM CONFIGURATION
os = var.proxmox_vm_os_type
cpu_type = var.proxmox_vm_cpu_type
sockets = var.proxmox_vm_cpu_sockets
cores = var.proxmox_vm_cpu_cores
memory = var.proxmox_vm_memory
network_adapters {
model = var.proxmox_vm_network_adapters.model
bridge = var.proxmox_vm_network_adapters.bridge
vlan_tag = var.proxmox_vm_network_adapters.vlan_tag
}
disks {
type = var.proxmox_vm_disks.type
disk_size = var.proxmox_vm_disks.disk_size
storage_pool = var.proxmox_vm_disks.storage_pool
storage_pool_type = var.proxmox_vm_disks.storage_pool_type
format = var.proxmox_vm_disks.format
}
# SSH CONNECTION
ssh_timeout = var.ssh_provisioner.timeout
ssh_username = var.ssh_provisioner.username
ssh_password = var.ssh_provisioner.password
}
http_content
Earlier, I would be providing the cloud-init data to the virtual machine using the configuration directive http_directory
.
This directive instructs Packer to start a webserver, serving static content from the directory specified. This is static content however, and cannot be made dynamic.
I tried thinking about ways to start an external webserver or write a script to make the identity part in user-data dynamic. Packer to the rescue!!!
The directive http_content
allows you to control which data is served by the webserver, while also being able to pass data from variables using the function templatefile()
:
http_content = {
"/meta-data" = file("cloud-init/meta-data")
"/user-data" = templatefile("cloud-init/user-data", { "provisioner_username" = var.ssh_provisioner.username, "provisioner_password" = bcrypt(var.ssh_provisioner.password, 10), "provisioner_sshkey" = var.ssh_provisioner.sshkey})
}
bcrypt()
function as well, to make sure the password is hashed and accepted by cloud-init. Without this call, the SSH connection will fail.The second argument of the function is a map of key-value pairs of strings to be replaced in the template file. In turn, the values are referencing variables from our Packer configuration.
user-data
#cloud-config
autoinstall:
version: 1
locale: en_US
keyboard:
layout: en
variant: us
source:
id: ubuntu-server-minimized
storage:
layout:
name: direct
identity:
hostname: packer-ubuntu-22-04-proxmox
username: ${provisioner_username}
password: ${provisioner_password}
ssh:
install-server: yes
allow-pw: yes
authorized-keys:
- ${provisioner_sshkey}
packages:
- apparmor-profiles
- apparmor-utils
- apt-transport-https
- ca-certificates
- cloud-init
- curl
- fail2ban
- gnupg2
- htop
- iftop
- net-tools
- ntp
- qemu-guest-agent
- screen
- traceroute
late-commands:
- echo 'Defaults:stager !requiretty' > /target/etc/sudoers.d/stager
- echo 'stager ALL=(ALL) NOPASSWD:ALL' >> /target/etc/sudoers.d/stager
- chmod 440 /target/etc/sudoers.d/stager
- curtin in-target --target=/target -- apt update
- curtin in-target --target=/target -- apt upgrade -y
Variables
Definitions
Packer requires all variables used through the configuration to be defined.
variable "proxmox_url" {
type = string
description = "URL or IP Address of the Proxmox API, without a HTTP PATH"
sensitive = false
}
variable "proxmox_port" {
type = number
default = 8006
description = "Port on which the Proxmox API listens"
sensitive = false
}
variable "proxmox_skip_certificate_validation" {
type = bool
default = false
description = "Skip SSL certificate validation when connecting to the Proxmox API"
sensitive = false
}
variable "proxmox_username" {
type = string
default = "root@pam"
description = "Proxmox API Username"
sensitive = true
}
variable "proxmox_password" {
type = string
description = "Proxmox API Password"
sensitive = true
}
variable "proxmox_node" {
type = string
description = "Proxmox Node Name that will run the build"
sensitive = false
}
variable "proxmox_iso_storage_name" {
type = string
default = "local"
description = "Name of the storage repository where the ISO-file can be found"
sensitive = false
}
variable "proxmox_iso_filename" {
type = string
description = "Filename of the ISO-file"
sensitive = false
}
variable "proxmox_iso_checksum" {
type = string
description = "Checksum for the ISO file"
sensitive = false
}
variable "proxmox_unmount_iso" {
type = bool
default = true
description = "Unmount the ISO-file when building is complete"
sensitive = false
}
variable "proxmox_template_name" {
type = string
default = "template_ubuntu_22_04"
description = "Template name when Packer has completed the build process"
sensitive = false
}
variable "proxmox_vm_name" {
type = string
default = "packer-ubuntu-22-04"
description = "Virtual machine name while Packer is running"
sensitive = false
}
variable "proxmox_vm_id" {
type = number
default = 999999001
description = "Virtual machine ID"
}
variable "proxmox_vm_storage_name" {
type = string
description = "Name of the storage pool to store the virtual machine"
sensitive = false
}
variable "proxmox_vm_os_type" {
type = string
default = "l26"
description = "Virtual machine type"
sensitive = false
}
variable "proxmox_vm_cpu_type" {
type = string
default = "kvm64"
description = "Virtual machine processor type"
sensitive = false
}
variable "proxmox_vm_cpu_sockets" {
type = number
default = 1
description = "Virtual machine CPU sockets"
sensitive = false
}
variable "proxmox_vm_cpu_cores" {
type = number
default = 2
description = "Virtal machine CPU cores"
sensitive = false
}
variable "proxmox_vm_memory" {
type = number
default = 4096
description = "Virtual machine memory"
sensitive = false
}
variable "proxmox_vm_network_adapters" {
type = object({
model = string
bridge = string
vlan_tag = number
})
default = {
model = "virtio"
bridge = ""
vlan_tag = 0
}
}
variable "proxmox_vm_disks" {
type = object({
type = string
disk_size = string
storage_pool = string
storage_pool_type = string
format = string
})
default = {
type = "virtio"
disk_size = "50G"
storage_pool = ""
storage_pool_type = ""
format = "qcow2"
}
}
variable "ssh_provisioner" {
type = object({
timeout = string
username = string
password = string
sshkey = string
})
default = {
timeout = "5m"
username = ""
password = ""
sshkey = ""
}
sensitive = true
}
variable "template_script_repository_url" {
type = string
default = "https://github.com/jantytgat/iac-scripts"
description = "URL of the Git-repository containing all template scripts to configure the image"
sensitive = false
}
variable "template_script_repository_name" {
type = string
default = "iac-scripts"
description = "Name of the Git-repository containing all template scripts to configure the image"
sensitive = false
}
Values
Here comes the bulk of the magic involved. Specifying values for the variables typically involves specifying sensitive data, such as usernames and passwords.
We will replace these values with a referency to an item in our 1Password vault.
proxmox_url = "{{ op://clcd-2023/hypervisor/website }}"
proxmox_skip_certificate_validation = true
proxmox_username = "{{ op://clcd-2023/hypervisor/username }}"
proxmox_password = "{{ op://clcd-2023/hypervisor/password }}"
proxmox_node = "{{ op://clcd-2023/hypervisor/Title }}"
proxmox_iso_storage_name = "local"
proxmox_iso_filename = "ubuntu-22.04.1-live-server-amd64.iso"
proxmox_iso_checksum = "sha256:10f19c5b2b8d6db711582e0e27f5116296c34fe4b313ba45f9b201a5007056cb"
proxmox_template_name = "template_ubuntu_22_04"
proxmox_vm_name = "packer-ubuntu-22-04"
proxmox_vm_storage_name = "local"
#proxmox_vm_storage_type = "nfs"
proxmox_vm_network_adapters = {
model = "virtio"
bridge = "vmbr1"
vlan_tag = 3142
}
proxmox_vm_disks = {
type = "virtio"
disk_size = "50G"
storage_pool = "local"
storage_pool_type = "directory"
format = "qcow2"
}
ssh_provisioner = {
timeout = "10m"
username = "{{ op://clcd-2023/provisioner/username }}"
password = "{{ op://clcd-2023/provisioner/password }}"
sshkey = "{{ op://clcd-2023/provisioner/public key }}"
}
1Password
To automate the required calls before Packer can run, I’ve created some scripts to be used in a Makefile
.
The two scripts are responsible for:
- Logging in to 1Password and injecting the sensitive data in a file called
build.auto.pkrvars.hcl
, which will automatically be picked up by Packer at runtime. - Cleaning up the generated file, so no sensitive data remains on the system after Packer is complete.
Makefile
#!/bin/sh
eval $(op signin)
cd packer
op inject -i build.pkrvars.hcl -o build.auto.pkrvars.hcl
packer build -timestamp-ui .
.gitignore
file makes sure no files with a name like *.auto.pkrvars.hcl
are committed to git.Execution
When running make proxmox-ubuntu-22-04
from the command-line:
- 1Password CLI will ask for your password
- 1Password CLI will inject all
{{ op:// }}
references into build.auto.pkrvars.hcl - Launch Packer
Resources
Internal
Title | Link |
---|---|
GitHub | https://github.com/jantytgat/continuouslearning |
Scripts for IAC | https://github.com/jantytgat/iac-scripts |
External
Title | Link |
---|---|
Packer | https://packer.io |
Packer - Documentation | https://developer.hashicorp.com/packer/docs |
Packer - templatefile | https://developer.hashicorp.com/packer/docs/templates/hcl_templates/functions/file/templatefile |
Packer - Assigning variables | https://developer.hashicorp.com/packer/guides/hcl/variables#assigning-variables |
1Password | https://1password.com |
1Password CLI - Documentation | https://developer.1password.com/docs/cli/ |
1Password CLI - Load secrets into config files | https://developer.1password.com/docs/cli/secrets-config-files |
gitignore | https://www.toptal.com/developers/gitignore |
gitignore - Packer template | https://www.toptal.com/developers/gitignore/api/packer |
Ubuntu autoinstall reference | https://ubuntu.com/server/docs/install/autoinstall-reference |