Using Chef Vault with TDD


Chef-vault is a way to store your ssh keys in the chef server. Chef vault uses a data bag to encrypt the keys and decrypts them when requested.

Suppose, we have a json file, secrets.json containing ssh keys pairs, for example

{
  "key_1_name": "secret_1",
  "key_2_name": "secret_2"
}

The first step would be encrypt these keys and store in the chef-vault. The keys will be stored in a databag within a dataset. Let us call our databag secrets and the dataset as application_secrets. An easy way to do create the databag at command line is,

bundle exec knife vault create secrets application_secrets --json ./secrets.json

This stores the secret keys in our chef-vault.

The next step would be to retrieve them in our recipe. We will use help of a community cookbook chef-vault to access chef-vault items.

Let us start by writing an integration test.

Integration testing

This integration test makes sure that there is a file on our chef-node when chef client runs on it. The chef-client run fetches the secrets keys from the chef server and dumps them in a file on the node. We will use kitchen and serverspec to do it.

Steps

  • Mock the databags for the kitchen vagrant machine. Let us specify the path for the mock databag item in the kitchen.yml file like this to be,
suites:
  - name: secrets_integration_test
    run_list: [ 'recipe[secrets::default]' ]
    attributes: {
      'dev_mode': true,
      'data_bags_path': 'test/integration/secrets_integration_test/data_bags',
  }
  • Make the mock databag file, test/integration/secrets_integration_test/data_bags/secrets/application_secrets.json like this,
{
  "test_key_1": "test_value_1",
  "test_key_2": "test_value_2"
}
  • The path of the databag file is comprised of the databag name secrets and the dataset name application_secrets.

The integration test will look something like this,

describe file('/application_secrets.json') do
  its(:content) { should match(/test_value_1/) }
  its(:content) { should match(/test_value_2/) }
end

Unit testing

Using chefspec, a unit test can be written like,

describe 'secrets::default' do
  let(:chef_run) { ChefSpec::Runner.new }

  before do
    allow(ChefVault::Item).to receive(:load).
      with('secrets', 'application_secrets').and_return({
        'test_key_1' => 'test_value_1',
        'test_key_2' => 'test_value_2'} )

    chef_run.converge(described_recipe)
  end

  it 'creates the application_secrets file with the secret key values in it' do
    expect(chef_run).to render_file('/application_secrets.json')
      .with_content(/test_value_1/)

    expect(chef_run).to render_file('/application_secrets.json')
      .with_content(/test_value_2/)
  end
end

#### Accessing secrets in the recipe

With the help of the chef_vault recipe, the secret keys can be easily accessed,

  my_secrets = chef_vault_item('secrets','application_secrets')

my_secrets is a ruby hash that can be used in the recipe as required.

A file can be rendered using this hash like this,

require 'json'

my_secrets = chef_vault_item('secrets','application_secrets')

file '/application_secrets.json' do
  content my_secrets.to_json
  mode '0640'
  group 'root'
  sensitive true
end
  • The block configuration sensitive true hides the content of the file, i.e the key values during a chef client run on STDOUT or in the logs. The content is just showed as hidden sensitive content

Related Posts

Making your own gem server

Host your gems locally in a co-working space