Tutorial - Puppet

Introduction

The conjur Puppet module provides a comprehensive solution for managing machine identity and distributing secrets through Puppet. Conjur + Puppet has clear advantages over other approaches to secrets management such as hiera-eyaml and hiera-vault:

  • Access to secrets is controlled separately for each node.
  • No “master key” is installed on the Puppet master; in fact, the Puppet master does not hold any long-lived key to the secrets vault at all.
  • Access to secrets is managed via machine identity and role-based access control policies, which are kept in source control.

As a result, the “blast radius” of a compromised node or Puppet master is minimized. Only those secrets available to a node are revealed to an attacker. A stolen backup of the Puppet master reveals no secrets at all.

Prerequisites

Overview

When we run Puppet, the manifest will perform the following steps:

  • Configure the client node’s connection to Conjur.
  • Assign an identity to the client node.
  • Authenticate the node with Conjur.
  • Fetch the database password from Conjur and merge this into a template file.
  • Store the file on the client node.

The conjur-conjur module provides supporting functions for these operations.

Load the Policy

As with all Conjur workflows, we begin by defining the policies.

Save this file as “conjur.yml”:

- !policy
  id: db
  body:
  - &variables
    - !variable password

  - !group secrets-users

  - !permit
    resource: *variables
    privileges: [ read, execute ]
    roles: !group secrets-users

- !policy
  id: myapp
  body:
  - !layer
    annotations:
      description: My application layer

  - !host-factory
    layers: [ !layer ]
  
- !grant
  role: !group db/secrets-users
  member: !layer myapp

It defines:

  • variable:db/password Contains the database password.
  • layer:myapp A layer (group of hosts) with access to the password.
  • host_factory:myapp Used to create individual hosts and enroll them into layer:myapp.

Load the policy using the following command:

$ conjur policy load --replace root conjur.yml
Loaded policy 'root'
{
  "created_roles": {
    "myorg:host:myapp-01": {
      "id": "myorg:host:myapp-01",
      "api_key": "1wgv7h2pw1vta2a7dnzk370ger03nnakkq33sex2a1jmbbnz3h8cye9"
    }
  },
  "version": 1
}

Load the Database Password

Next, we need to populate the database password with a secret value. Use the CLI to verify that the variable exists in Conjur:

$ conjur list -k variable
[
  "myorg:variable:db/password"
]

Now, use OpenSSL to generate a random secret, and load it into the variable:

$ password=$(openssl rand -hex 12)
$ echo $password
ac8932bccf835a5a13586100
$ conjur variable values add db/password $password
Value added
$ conjur variable value db/password
ac8932bccf835a5a13586100

Create the Host Factory Token

In the introduction, we mentioned that the Puppet manifest assigns a Conjur identity to the client node. For this purpose, we use the Conjur Host Factory.

Create a host factory token for use by Puppet:

$ conjur hostfactory tokens create myapp
[
  {
    "token": "1axrq3g2cybym19qkhrc2z5kd5j3btcmwy3fyxngh22rvxrw1jh7d32",
    "expiration": "2017-05-24T14:13:20+00:00",
    "cidr": [

    ]
  }
]

The token that you see above can be used to enroll machines into the “myapp” layer.

Create the Manifest

Now it’s time to build the Puppet manifest. The manifest needs to do two things:

  1. Configure the connection to Conjur.
  2. Assign the machine identity.

For the first task, we supply the appropriate values for account and appliance_url. For the second, we use the host factory token. The manifest uses the host factory token to create a Conjur host called “myapp-01” which belongs to the myapp layer. Then it uses the privileges granted to the host by layer membership to fetch the database password.

Create the following Puppet manifest:

class { conjur:
  account         => 'dev',
  appliance_url   => 'http://conjur',
  authn_login     => 'host/myapp-01',
  host_factory_token => Sensitive('1axrq3g2cybym19qkhrc2z5kd5j3btcmwy3fyxngh22rvxrw1jh7d32')
}    

file { '/tmp/dbpass':
  ensure    => file,
  content   => conjur::secret('db/password'),
  show_diff => false,  # don't log file content!
}

Install the conjur Module

For the manifest to work, you need to install the conjur-conjur Puppet module:

$ puppet module install conjur-conjur
Notice: Preparing to install into /etc/puppetlabs/code/environments/production/modules ...
Notice: Downloading from https://forgeapi.puppet.com ...
Notice: Installing -- do not interrupt ...
/etc/puppetlabs/code/environments/production/modules
└── conjur-conjur (v2.0.0)

Run Puppet

Now, run Puppet:

$ puppet apply manifest.pp
...
TODO: show output

TODO: success message