There are many benefits to the automation of container deployment, but these benefits do not come without their complications. The DevOps efforts have made hard coding credentials into cloud-init scripts common practice, but this poses major security risks. Moreover, what if you need to get a certificate on to one of these instances? Do you save it into an image or configuration file? This poses even greater risk, as an adversary now has access to an exportable private key. Using tools like Puppet or Ansible conjointly with our Keyfactor Command platform, we can mitigate these risks and request the certificate uniquely for the container or virtual machine at the time of its creation. This post will demonstrate this concept in the context of Microsoft Azure VMs.
Container administration requires that you have a host system (Azure recommends CentOS 7.4, Ubuntu 16.04 LTS, or SLES 12 SP2) with Ansible/Puppet, the Python SDK, and The Azure CLI 2.0. In this example, I spun up a Standard B1 running Ubuntu 16.04 LTS and ran:
# install required packages
sudo apt-get update && sudo apt-get install -y libssl-dev libffi-dev python-dev python-pip
sudo pip install ansible[azure]
# add Azure CLI repo to package manager
AZ_REPO=$(lsb_release -cs)
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | \
sudo tee /etc/apt/sources.list.d/azure-cli.list
curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add –
# install Azure CLI
sudo apt-get install apt-transport-https
sudo apt-get update && sudo apt-get install azure-cli
# the VM we will spin up will be running the yum package manager so we will need it on the host
# as well to acquire the proper Python bindings
sudo apt-get install yum
After configuring the host machine, the next step is to configure the Azure CLI to allow you to programmatically create new VMs. To do this, run:
az login
Next, enter the given code at https://microsoft.com/devicelogin after authenticating to your Azure portal as the user you wish to run Ansible as. You should get a result that looks like:
[
{
"cloudName": "AzureCloud",
"id": " f9c3e574-235a-4998-a604-8a13b91e2100",
"isDefault": true,
"name": "Your Subscription Name"
"state": "Enabled",
"tenantId": " 4c00ebbc-e7cc-4790-bbbb-71dc5fb948e2",
"user": {
"name": "[email protected]",
"type": "user"
}
}
]
Now Ansible will require us to have an SSH key/secret pair to connect to the new VM. We can generate this with
ssh-keygen -t rsa -C [email protected]
Providing an email will help us identify the key in the future. If you save this at the default location, /home/(current user)/.ssh/id_rsa, your public key should reside in /home/iamgroot/.ssh/id_rsa.pub. You’ll want to leverage the SSH Agent if you protected your private key with a passphrase. The agent helps with ssh key management when you’re doing things around automation. To add the private key to the agent, just run:
ssh-agent bash
ssh-agent add <Path to your Private Key>
Now we can create the Ansible Playbook. This will allow us to spin up an azure VM, pull down a python script, and request a certificate with a single command. Create a new file with a .yml extension, and configure the following:
- name: Provision Azure VM with Certificate
hosts: localhost
connection: local
tasks:
# The first thing the play will do is spin up
# a new Azure Virtual Machine on the same
# network.
- name: Create public IP address
azure_rm_publicipaddress:
resource_group: AnsibleTest-Jake
allocation_method: Static
name: childVM1-ip
- name: Create Network Security Group that allows SSH
azure_rm_securitygroup:
resource_group: AnsibleTest-Jake
name: childVM1-nsg
rules:
- name: SSH
protocol: Tcp
destination_port_range: 22
access: Allow
priority: 1001
direction: Inbound
- name: Create virtual network inteface card
azure_rm_networkinterface:
resource_group: AnsibleTest-Jake
name: childVM1-nic
virtual_network: AnsibleTest-Jake-vnet
public_ip_name: childVM1-ip
subnet_name: default
security_group: childVM1-nsg
- name: Create VM
azure_rm_virtualmachine:
resource_group: AnsibleTest-Jake
name: childVM1
vm_size: Standard_DS1_v2
admin_username: azureuser
ssh_password_enabled: false
ssh_public_keys: # the ssh key used here is found in /home/<Your User>/.ssh/id_rsa.pub
- path: /home/azureuser/.ssh/authorized_keys
key_data: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDr4kQvWCZcqDbNOFHumbHUi1x6LAdeRImjmz79+srshS14cHsLD1qX5ATOhxWTZtfXZUG9UnlB2ag5I15b3xEVpo/eX9CnTMKWKpkiaQIfQgb20EdFgWE8dP9VXvdfTpJBGLOYKcmUbn/BnkKp3B1BWkp0YRiV8S1r1yYZxyFMGPcOJqwHwFyvJjxR8bGNIFWZ6tdZa+MCkoxSKk9IeIsCEHZhCTrMPceUZnzbKIU0waiwvnidZkRXjj//rQcDrcWyrem6pHS03f/Dt+1LpU50ahtp8we/SnlQNT+nYPiNcSzTSicPe0O1imSCB63bHLmtW/SRFTr8Yhsvq28VVAkV [email protected]"
network_interfaces: childVM1-nic
image:
offer: CentOS
publisher: OpenLogic
sku: '7.5'
version: latest
# in order to connect to the new host with Ansible,
# we must tell Ansible that it exists
- name: Get childVM1 IP to add to Ansible Inventory
azure_rm_publicipaddress_facts:
resource_group: AnsibleTest-Jake
name: childVM1-ip
register: azure_ip
- debug: var=azure_ip
- name: Add childVM1 to Ansible Inventory
add_host:
hostname: "{ { item.properties.ipAddress } }"
groupname: launched
ansible_ssh_private_key_file: ~/.ssh/id_rsa
ansible_ssh_user: azureuser
with_items: "{ { azure_ip.ansible_facts.azure_publicipaddresses } }"
- name: Add Epel Repository to Remote Host
yum_repository:
name: epel
description: EPEL YUM repo
baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
- name: Update Yum on Remote Host
yum:
name: '*'
state: latest
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
- name: Install Git on Remote Host
yum:
name: git
state: latest
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
- name: Create Git Download Directory
file: path=/home/azureuser/gitdl state=directory
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
# download python file from git repo
- name: Download API Result
git:
repo: https://gitlab.com/css-jadkins/Python-CMS-API-Call.git
dest: /home/azureuser/gitdl
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
- name: Set Execute Permissions
file:
path: /home/azureuser/gitdl/Enroll-PKCS12.py
owner: azureuser
mode: 0650
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
- name: Execute Python Script
command: python /home/azureuser/gitdl/Enroll-PKCS12.py
become: true
become_user: root
become_method: sudo
delegate_to: "{ { azure_ip.ansible_facts.azure_publicipaddresses[0].properties.ipAddress } }"
Now run the ansible playbook:
ansible-playbook azure_create_vm_basic.yml
Once complete, you will be able to sign into childVM1 using the given ssh uri (from the VM’s overview in the Azure portal). The dialog will look something like this:
[azureuser@childVM1 ~]$ ssh [email protected]
The authenticity of host '137.116.85.88 (137.116.85.88)' can't be established.
ECDSA key fingerprint is SHA256:jJt9S1CVT/Mz8a4aC+l4G3fzbP1VBItlumbyvTEhGdg.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '137.116.85.88' (ECDSA) to the list of known hosts.
Enter passphrase for key '/home/iamgroot/.ssh/id_rsa': *************
[azureuser@childVM1 ~]$
You’ll find that your .pfx file now resides in your home directory. Please note, you may need to add pip modules to Ansible to resolve Python dependencies depending on your flavor of Linux.
The Python script used in this example can be found at: https://gitlab.com/css-jadkins/Python-CMS-API-Call.