DevOps: How to Securely Spin Up Containers using CMS, Puppet, and Ansible

DevOps: How to Securely Spin Up Containers using CMS, Puppet, and Ansible

Posted by Jake Adkins on Aug 13, 2018 4:05:25 PM

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 Certificate Management System (CMS) 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.

How to Securely Spin Up Containers using CMS, Puppet, and Ansible

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": "youruser@organization.com",

      "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 youruser@organization.com

 

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 test@ex.com"

      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 azureuser@137.116.85.88

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.

 

Topics: DevOps, Certificate Management System (CMS), Puppet, Ansible

Recent Posts

Posts by Topic

see all

Subscribe to Email Updates

Want to Learn more about CSS?