diff --git a/.kitchen.yml b/.kitchen.yml index e1e5d7e..9df07ca 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -4,10 +4,26 @@ driver: provisioner: name: chef_zero + nodes_path: test/fixtures/nodes + clients_path: test/fixtures/clients + environments_path: test/fixtures/environments + data_bags_path: test/fixtures/data_bags + + # You many wish to test your CHEF::Log. messages while using test-kitchen. Change the below + # value to the level of choice. For cleaner output, comment this option out. + log_level: info + # You may wish to disable always updating cookbooks in CI or other testing environments. # For example: # always_update_cookbooks: <%= !ENV['CI'] %> always_update_cookbooks: true + retry_on_exit_code: # https://discourse.chef.io/t/test-kitchen-1-10-0-released/8721 + - 35 # 35 is the exit code signaling that the node is rebooting + max_retries: 2 + client_rb: + environment: test + exit_status: :enabled # Opt-in to the standardized exit codes + client_fork: false # Forked instances don't return the real exit code verifier: name: inspec @@ -19,8 +35,11 @@ platforms: suites: - name: default run_list: - - recipe[secrets_management::default] + - recipe[secrets_management_test::default] verifier: inspec_tests: - - test/smoke/default + - test/smoke/secrets_management_tests attributes: + hashicorp: + token: <%= ENV['VAULT_TOKEN'] %> + address: <%= ENV['VAULT_ADDR'] %> diff --git a/Berksfile b/Berksfile index 296e5e5..90bc219 100644 --- a/Berksfile +++ b/Berksfile @@ -9,3 +9,7 @@ source 'https://supermarket.chef.io' metadata + +group :test do + cookbook 'secrets_management_test', path: 'test/fixtures/cookbooks/test' +end diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..45dcbee --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# secrets_management Cookbook CHANGELOG + +This file is used to list changes made in each version of the secrets_management cookbook. + +## v1.0.0 + +- Initial release with support for reading Hashicorp Vault, Chef-Vault, and Chef Data bag items diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b99c1ce --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Please refer to https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD diff --git a/Gemfile b/Gemfile index 4cb75b4..7bc5095 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,10 @@ source 'https://rubygems.org' +gem 'vault', '~> 0.1' + group :test do gem 'fauxhai' gem 'tomlrb' + gem 'webmock' end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ce5eaae --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2017 Exosphere Data, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index fdbcf1f..0ee4e28 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,122 @@ # secrets_management -### _Installs/Configures secrets_management_ +### _A Chef library for global secrets management_ -TODO: Enter the cookbook description here. +This cookbook provides a Ruby library helper to support management of Hashicorp Vault, Chef Vault, and Chef DataBag items. This cookbook does not include resources or recipes. The purpose of this project is to simplify the handling of secrets and data management by integrating a single method whereby Hashicorp Vault, Chef Vault, and Chef DataBag items can be managed. - - **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* -- [secrets_management](#secrets_management) - - [Requirements](#requirements) - - [Platforms](#platforms) - - [Chef](#chef) - - [Cookbooks](#cookbooks) - - [Data bag](#data-bag) - - [Attributes](#attributes) - - [Usage](#usage) - - [default](#default) - - [Upload to Chef Server](#upload-to-chef-server) - - [Matchers/Helpers](#matchershelpers) - - [Matchers](#matchers) - - [Helpers](#helpers) - - [Cookbook Testing](#cookbook-testing) - - [Before you begin](#before-you-begin) - - [Data_bags for Test-Kitchen](#data_bags-for-test-kitchen) - - [Rakefile and Tasks](#rakefile-and-tasks) - - [Chefspec and Test-Kitchen](#chefspec-and-test-kitchen) - - [Compliance Profile](#compliance-profile) - - [Copyright:: 2017, Exosphere Data LLC, All Rights Reserved.](#copyright-2017-exosphere-data-llc-all-rights-reserved) - - +- [Requirements](#requirements) + - [Platforms](#platforms) + - [Chef](#chef) + - [Cookbooks](#cookbooks) +- [Usage](#usage) + - [default](#default) +- [Libraries](#libraries) + - [SecretsManagement::DSL](#secretsmanagementdsl) +- [Upload to Chef Server](#upload-to-chef-server) +- [Upload to Private Chef Supermarket](#upload-to-private-chef-supermarket) +- [Cookbook Testing](#cookbook-testing) + - [Before you begin](#before-you-begin) + - [Data_bags for Test-Kitchen](#data_bags-for-test-kitchen) + - [Rakefile and Tasks](#rakefile-and-tasks) + - [Chefspec and Test-Kitchen](#chefspec-and-test-kitchen) + - [Test Cookbook (secrets_management_test)](#test-cookbook-secrets_management_test) + - [Compliance Profile](#compliance-profile) +- [Contribute](#contribute) +- [License & Authors](#license-&-authors) ## Requirements ### Platforms -TODO: List all supported Platforms and versions. +This resource should work on any Chef supported platform with a Chef Client meeting the minimum requirements. ### Chef - 12.5+ -TODO: Minimum supported version of CHEF client supported by this cookbook - ### Cookbooks -TODO: Identify any cookbook dependencies +- chef-vault, '~> 3.0' -### Data bag +## Usage +To use the libraries, declare a dependency on this cookbook, and then use the libary as described in the section [SecretsManagement::DSL](#secretsmanagementdsl). -TODO: List all supported data_bag names, model, and items. +### default -## Attributes +This is an empty recipe and should _not_ be modified. -TODO: Enter all available node attributes including description, field type, and default value. +## Libraries +### SecretsManagement::DSL -## Usage -### default +#### open_secret_item +The `open_secret_item` method supports accessing existing Hashicorp Vault, Chef Vault, and Chef DataBag items. The method supports two possible models for getting the data - `determine_bag_type` or `find__item`. By default, the method will attempt to perform the lookup unless the attribute `:type` is sent. -This is an empty recipe and should _not_ be used +Properties: -TODO: Write descriptions about any included recipes +| Name | Description | Type | Mandatory | +| --- | --- | --- | --- | +| **container** | Path to Hashicorp Vault or the Name of the Chef Vault or DataBag | String | X | +| **item** | Item name in Vault or Bag | String | X | +| type | Supported values: `vault` (Hashicorp), `chef_vault`, or `data_bag` | String | | +| vault | Hash of supported keys for accessing Hashicorp environment. Minimum required keys are `address` and `token`. | Hash | | -## Upload to Chef Server -This cookbook should be included in each organization of your CHEF environment. When importing, leverage Berkshelf: +_Note: When returning details from Hashicorp Vault, this library will normalize the key names as strings. By default, the keys will be returned as a symbol. To keep this output consistent across the ChefVault and DataBag models, the library converts the keys from symbols to strings._ -`berks upload` +#### Examples -_NOTE:_ use the --no-ssl-verify switch if the CHEF server in question has a self-signed SSL certificate. +```ruby +# Open a secret item based on testing the options - vault, chef_vault, then data_bag +bag = open_secret_item('secret', 'item') + +# Include a vault object to support looking into Hashicorp as part of the lookup +bag = open_secret_item('secret', 'item', vault: { 'token' => '1234', 'address' => 'http://192.168.0.1:8200' }) +``` + +```ruby +# Look up a data_bag item +bag = open_secret_item('simple', 'item', type: 'data_bag') +``` + +```ruby +# Lookup a chef_vault item +bag = open_secret_item('secrets', 'bacon', type: 'chef_vault') +``` + +```ruby +# Lookup a chef_vault item and use the output to access a Hashicorp Vault item +vault_hash = open_secret_item('vault', 'secret', type: 'chef_vault') +bag = open_secret_item('secret/chef/os', 'windows', type: 'vault', vault: vault_hash) +``` -`berks upload --no-ssl-verify` +## Upload to Chef Server +This cookbook should be included in each organization of your CHEF environment. When importing, leverage Berkshelf: -## Matchers/Helpers +`berks upload --except test` -### Matchers -_Note: Matchers should always be created in `libraries/matchers.rb` and used for validating calls to LWRP_ +_NOTE:_ use the --no-ssl-verify switch if the CHEF server in question has a self-signed SSL certificate. -TODO: Add details about any matcher files included in this cookbook +`berks upload --no-ssl-verify --except test` +## Upload to Private Chef Supermarket +_NOTE:_ You must set the following key `knife[:supermarket_site] = 'https://'`. -### Helpers +This cookbook should be uploaded to the CHEF Supermarket server. When importing, leverage Berkshelf vendor command: -TODO: Add details about any helper files included in this cookbook +```bash +# From a Linux/Mac host via Bash +berks vendor .bundle +for i in `ls .bundle`; do knife cookbook site share $i "Other" -o .bundle; done +``` +or +```powershell +# From a Windows host via PowerShell +berks vendor .bundle +foreach ($i in (Get-ChildItem -Path .bundle) ){ + knife cookbook site share $i "Other" -o .bundle +} +``` ## Cookbook Testing @@ -103,16 +138,18 @@ This cookbook requires the use of a data_bag for setting certain values. Local │ ├── cookbooks │ │ ├── secrets_management │ │ │ ├── .kitchen.yml -│ ├── data_bags -│ │ ├── data_bag_name -│ │ │ ├── data_bag_item.json +│ │ │ ├── test +│ │ │ │ ├── fixtures +│ │ │ │ │ ├── data_bags +│ │ │ │ │ │ ├── data_bag_name +│ │ │ │ │ │ │ ├── data_bag_item.json ``` -**Note**: Storing local testing versions of the data_bags at the root of your repo is considered best practice. This ensures that you only need to maintain a single copy while protecting the cookbook from being accientally committed with the data_bag. However, if you must change this location, then update the following key in the .kitchen.yml file. +**Note**: Storing local testing versions of the data_bags at the root of your repo is considered best practice. This ensures that you only need to maintain a single copy while protecting the cookbook from being accientally committed with the data_bag. However, since this cookbook contains no recipes, we have included the test data_bags for Kitchen purposes. If you must change this location, then update the following key in the .kitchen.yml file. ``` -data_bags_path: "../../data_bags/" +data_bags_path: "test/fixtures/data_bags/" ``` ### Rakefile and Tasks @@ -136,10 +173,46 @@ This repo includes a **Rakefile** for common tasks 2. `berks install`: Installs all cookbook dependencies based on the [Berksfile](Berksfile) and the [metadata.rb](metadata.rb) 3. `rake`: This will run all of the local tests - syntax, lint, unit, and maintainers file. -4. `rake integration`: This will run all of the kitchen tests +4. `rake integration`: This will run all of the kitchen tests + +### Test Cookbook (secrets_management_test) +_a test cookbook for the available LWRPs_ + +The cookbook secrets_management does not include any executable recipes as it is designed to be an utility cookbook and support other initiatives. For the purposes of testing and validating this code, we have included a test cookbook with pre-configured recipes. + +| **Name** | **Description** | +| ------------- |-------------| +| _Default_ | Roll-up recipe to test all of the functionality of the LWRP-specific recipes | +| _hashivault_ | Test gathering secrets from Hashicorp Vault environments. | +| _chef_vault_ | Test gathering secrets from ChefVault bags | +| _data_bag_ | Test gathering secrets from Chef DataBags | + ### Compliance Profile -Included in this cookbook is a set of Inspec profile tests used for supported platforms in Test-Kitchen. These profiles can also be loaded into Chef Compliance to ensure on-going validation. The Control files are located at `test/smoke/suite_name` +Not included as this is a Resource only cookbook with no included recipes. + +## Contribute + - Fork it + - Create your feature branch (git checkout -b my-new-feature) + - Commit your changes (git commit -am 'Add some feature') + - Push to the branch (git push origin my-new-feature) + - Create new Pull Request + +## License & Authors +**Author:** Jeremy Goodrum ([jeremy@exospheredata.com](mailto:jeremy@exospheredata.com)) -## Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. +**Copyright:** 2017 Exosphere Data, LLC + +```text +Copyright 2017 Exosphere Data, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +``` diff --git a/attributes/default.rb b/attributes/default.rb new file mode 100644 index 0000000..ce2e366 --- /dev/null +++ b/attributes/default.rb @@ -0,0 +1,10 @@ +# +# Cookbook:: secrets_management +# Attribute:: default +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +default['hashicorp']['refresh_token'] = nil diff --git a/libraries/dsl.rb b/libraries/dsl.rb new file mode 100644 index 0000000..f63fea4 --- /dev/null +++ b/libraries/dsl.rb @@ -0,0 +1,166 @@ +# +# Library:: secrets_management::dsl +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +module SecretsManagement + module DSL + require 'vault' + + # The 'open_secret_item' method allows us to dynamically handle the opening and error_handling + # of several types of secrets options. The method will wrap the individual calls and return + # exceptions or data depending on the validity of the item's return. + def open_secret_item(container, item, bag_type: nil, vault: {}) + # If the bag_type key is not set, then we should be able to attempt to figure it out by + # testing the different available methods. We will do this first before testing + # the case statement + return determine_container_type(container, item, vault: vault) if bag_type.nil? + + Chef::Log.debug("Checking for the secret bag_type #{bag_type}") + + # Since, the 'bag_type' parameter was sent, we will use a case statement to look + # up the individual type method and return an output. If an invalid bag_type is sent, + # We will raise a clearly documented error. + case bag_type + when 'vault' + bag_item = find_hashicorp_vault_item(container, item, vault) + when 'chef_vault' + bag_item = find_chef_vault_item(container, item) + when 'data_bag' + bag_item = find_data_bag_item(container, item) + else + raise ArgumentError, "An invalid secret type (#{bag_type}) has been provided for ('#{container}','#{item}'). Currently, the only supported versions are 'vault', 'chef_vault', and 'data_bag'" + end + + # If the returned data does not contain and error message, then return the data. Otherwise, + # we need to raise a clearly documented exception as an ArgumentError. + return bag_item unless bag_item.key?(:error) + raise ArgumentError, bag_item[:error] + end + + private + + # The 'determine_container_type' method will make multiple calls to each supported bag_type + # method in the private module methods to find the most appropriate item type. We are + # setting the vault parameter to an empty hash by default in the event that the data is empty. + # This action solves for an empty call and keeps the errors clean. + def determine_container_type(container, item, vault: {}) + Chef::Log.debug("Attempting to determine the bag type for #{container}") + + # For each type that we test, we should monitor for an exception. Since we are looking + # up the type, we will ignore any valid responses with a error message in the output. + bag_item = find_hashicorp_vault_item(container, item, vault) + return bag_item unless bag_item.key?(:error) + raise ArgumentError, bag_item[:error] if bag_item.key?(:error) && container.include?('/') + + unless container.include?('/') + bag_item = find_chef_vault_item(container, item) + return bag_item unless bag_item.key?(:error) && bag_item != {} + + # Finally, test to see if this is a basic data bag item + bag_item = find_data_bag_item(container, item) + return bag_item unless bag_item.key?(:error) + end + + raise ArgumentError, "Failure: Unable to determine the bag_type or retrieve the bag_item (\'#{container}\',\'#{item}\'). This item might not exist." + end + + def find_hashicorp_vault_item(bag, item, vault_hash) + Chef::Log.debug("Gathering the details for HashicorpVaultItem(\'#{bag}\',\'#{item}\')") + + # To support both string and symbol keys, we will just convert the hash keys by default. + vault_hash = symbolize_keys(vault_hash) + + # We need to declare this variable as an empty hash to prevent unknown type errors + # when testing later. This is due to wrapping the vault calls with retries. + bag_item = {} + begin + # This check is useful when we are trying to determine the bag bag_type. + return { error: 'You did not provide details for the Hashicorp Vault server.' } if vault_hash.empty? || vault_hash[:token].nil? || vault_hash[:address].nil? + vault = create_hashicorp_client(vault_hash) + + # If the node attribute ['hashicorp']['refresh_token'] is set or we receive the information + # in the hash, then we will request that the token be refreshed to a maximum of the value provided. + # However, this value cannot exceed the configured default token TTL. + token_refresh = vault_hash['renew_token'] || nil + token_refresh = node['hashicorp']['refresh_token'] unless node['hashicorp']['refresh_token'].nil? + + Chef::Log.debug("Renewing the Vault token: #{token_refresh}") unless token_refresh.nil? + vault.auth_token.renew_self token_refresh unless token_refresh.nil? + + vault.with_retries(Vault::HTTPConnectionError, Vault::HTTPError) do |attempt, e| + log "Received exception #{e} from Vault - attempt #{attempt}" if e + bag_item = vault.logical.read("#{bag}/#{item}") + end + + # Since Vault doesn't send an error when the item is not found, we will need to force + # the system to error out if the variable 'bag_item' is empty or nil. + raise '404' if bag_item.nil? + rescue Vault::HTTPConnectionError, + Vault::HTTPServerError, + Vault::HTTPClientError => error + raise error + rescue StandardError => error + # Any unexplained error should be thrown immediately. Otherwise, a 404 should be graceful + # raise error.inspect + raise error.message unless error.nil? || error.message == '404' + bag_item = { error: "This action requires that the HashicorpVaultItem(\'#{bag}/#{item}\') exists and that this system can access it. We failed to find the required items." } + return bag_item + end + + bag_data = {} + # Return the data from the Vault and not the Vault object + bag_item.data.each do |k, v| + bag_data[k.to_s] = v + end + bag_data + end + + def find_chef_vault_item(bag, item) + Chef::Log.debug("Gathering the details for ChefVaultItem(\'#{bag}\',\'#{item}\')") + begin + bag_item = chef_vault_item(bag, item) + raise if bag_item.nil? + rescue ChefVault::Exceptions::SecretDecryption => e + raise ArgumentError, "ChefVault::Exceptions::SecretDecryption: #{e.message}" + rescue Net::HTTPServerException + bag_item = { error: "This action requires that the ChefVaultItem(\'#{bag}\',\'#{item}\') exists and that this system can access it. We failed to find the required item." } + rescue StandardError + bag_item = { error: "This action requires that the ChefVaultItem(\'#{bag}\',\'#{item}\') exists and that this system can access it. We failed to find the required items." } + return bag_item + end + bag_item # Return the data from the bag and not the bag object + end + + def find_data_bag_item(bag, item) + Chef::Log.debug("Gathering the details for DataBagItem(\'#{bag}\',\'#{item}\')") + begin + bag_item = data_bag_item(bag, item) + raise if bag_item.nil? + rescue StandardError + bag_item = { error: "This action requires that the DataBagItem(\'#{bag}\',\'#{item}\') exists but it was not found." } + return bag_item + end + bag_item # Return the data from the bag and not the bag object + end + + def create_hashicorp_client(vault_hash) + # We shouldn't get to this point without the keys as symbols but just in case. + Vault::Client.new(symbolize_keys(vault_hash)) + end + + def symbolize_keys(hash_var) + new_hash = {} + hash_var.each do |k, v| + new_hash[k.to_sym] = v + end + new_hash + end + end +end + +Chef::Recipe.send(:include, SecretsManagement::DSL) +Chef::Resource.send(:include, SecretsManagement::DSL) diff --git a/metadata.rb b/metadata.rb index 30d8894..f6c5fa0 100644 --- a/metadata.rb +++ b/metadata.rb @@ -1,20 +1,30 @@ name 'secrets_management' maintainer 'Exosphere Data, LLC' maintainer_email 'chef@exospheredata.com' -license 'all_rights' -description 'Installs/Configures secrets_management' -long_description 'Installs/Configures secrets_management' -version '0.1.0' +license 'MIT' +description 'A resource provider for global secrets management' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '1.0.0' chef_version '>= 12.5' if respond_to?(:chef_version) +%w(debian ubuntu centos redhat amazon windows).each do |os| + supports os +end + +# Required for integratin with Chef-Vault +depends 'chef-vault', '~> 3.0' + +# Required for integration with Hashicorp Vault +gem 'vault', '~> 0.1' + # The `issues_url` points to the location where issues for this cookbook are # tracked. A `View Issues` link will be displayed on this cookbook's page when # uploaded to a Supermarket. # -# issues_url 'https://github.com//secrets_management/issues' if respond_to?(:issues_url) +issues_url 'https://github.com/exospheredata/secrets_management/issues' if respond_to?(:issues_url) # The `source_url` points to the development reposiory for this cookbook. A # `View Source` link will be displayed on this cookbook's page when uploaded to # a Supermarket. # -# source_url 'https://github.com//secrets_management' if respond_to?(:source_url) +source_url 'https://github.com/exospheredata/secrets_management' if respond_to?(:source_url) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b7af5f0..f610527 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,9 @@ require 'chefspec' require 'chefspec/berkshelf' -require_relative 'windows_helper.rb' +require 'webmock/rspec' +require 'vault' +require 'chef-vault' + +WebMock.disable_net_connect!(allow_localhost: true, allow: 'supermarket.chef.io') at_exit { ChefSpec::Coverage.report! } diff --git a/spec/unit/recipes/chef_vault_spec.rb b/spec/unit/recipes/chef_vault_spec.rb new file mode 100644 index 0000000..e03684e --- /dev/null +++ b/spec/unit/recipes/chef_vault_spec.rb @@ -0,0 +1,58 @@ +# +# Cookbook:: secrets_management +# Spec:: default_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'secrets_management_test::chef_vault' do + before do + # Need to perform the following in order to stub out the chef-vault items. + allow(ChefVault::Item).to( + receive(:vault?).with('secrets', 'bacon').and_return(true) + ) + allow(ChefVault::Item) + .to receive(:load).with('secrets', 'bacon').and_return('id' => 'bacon', + 'password' => 'my_super_secret') + allow(Chef::DataBag) + .to receive(:load).with('secrets').and_return('bacon_keys' => {}) + end + context 'Validate supported installations' do + platforms = { + 'redhat' => { + 'versions' => %w(7.3) + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + context 'When all attributes are default' do + before do + Fauxhai.mock(platform: platform, version: version) + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + expect(chef_run).to include_recipe('secrets_management_test::chef_vault') + expect(chef_run).to write_log('Test a single chef_vault item') + expect(chef_run).to write_log('Test a direct check chef_vault item') + expect(chef_run).to create_file('/tmp/cache/chef_vault.test').with(content: 'my_super_secret') + end + end + end + end + end + end +end diff --git a/spec/unit/recipes/data_bag_spec.rb b/spec/unit/recipes/data_bag_spec.rb new file mode 100644 index 0000000..60fd4c9 --- /dev/null +++ b/spec/unit/recipes/data_bag_spec.rb @@ -0,0 +1,51 @@ +# +# Cookbook:: secrets_management +# Spec:: default_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'secrets_management_test::data_bag' do + before do + stub_data_bag_item('simple', 'item').and_return( + 'test_key' => 'value1' + ) + end + context 'Validate supported installations' do + platforms = { + 'redhat' => { + 'versions' => %w(7.3) + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + context 'When all attributes are default' do + before do + Fauxhai.mock(platform: platform, version: version) + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + expect(chef_run).to include_recipe('secrets_management_test::data_bag') + expect(chef_run).to write_log('Test a direct check data_bag item') + expect(chef_run).to create_file('/tmp/cache/data_bag.test').with(content: 'value1') + end + end + end + end + end + end +end diff --git a/spec/unit/recipes/default_spec.rb b/spec/unit/recipes/default_spec.rb index 08dafbb..3acd5ee 100644 --- a/spec/unit/recipes/default_spec.rb +++ b/spec/unit/recipes/default_spec.rb @@ -9,37 +9,79 @@ require 'spec_helper' -describe 'secrets_management::default' do - context 'Install prequisite components' do +describe 'secrets_management_test::default' do + before do + # Hashivault stubs using webmocks + response_headers = { 'Content-Type' => 'application/json' } + vault_found = { request_id: '12345', lease_id: '', renewable: false, lease_duration: 2764800, data: { demo: true, test_key: '42' }, wrap_info: nil, warnings: nil, auth: nil } + + stub_request(:get, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_1').to_return(status: 200, body: vault_found.to_json, headers: response_headers) + stub_request(:put, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_1').with(body: '{"demo":true,"test_key":"42"}').to_return(status: 204, body: '', headers: response_headers) + + vault_found = { request_id: '12345', lease_id: '', renewable: false, lease_duration: 2764800, data: { demo: true, test_key: '84' }, wrap_info: nil, warnings: nil, auth: nil } + + stub_request(:get, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_2').to_return(status: 200, body: vault_found.to_json, headers: response_headers) + stub_request(:put, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_2').with(body: '{"demo":true,"test_key":"84"}').to_return(status: 204, body: '', headers: response_headers) + + # Need to perform the following in order to stub out the chef-vault items. + allow(ChefVault::Item).to( + receive(:vault?).with('secrets', 'bacon').and_return(true) + ) + allow(ChefVault::Item) + .to receive(:load).with('secrets', 'bacon').and_return('id' => 'bacon', + 'password' => 'my_super_secret') + allow(Chef::DataBag) + .to receive(:load).with('secrets').and_return('bacon_keys' => {}) + + stub_data_bag_item('simple', 'item').and_return( + 'test_key' => 'value1' + ) + + stub_request(:get, 'http://192.168.0.1:8200/v1/secrets/token').to_return(status: 404, body: {}.to_json, headers: response_headers) + allow(ChefVault::Item).to( + receive(:vault?).with('secrets', 'token').and_return(true) + ) + allow(ChefVault::Item) + .to receive(:load).with('secrets', 'token').and_return('id' => 'token', + 'token' => 'my_super_secret', + 'address' => 'http://192.168.0.1:8200') + allow(Chef::DataBag) + .to receive(:load).with('secrets').and_return('token_keys' => {}) + end + context 'Validate supported installations' do platforms = { - 'ubuntu' => { - 'versions' => %w(14.04 16.04) - }, - 'debian' => { - 'versions' => %w(7.8) - }, - 'centos' => { - 'versions' => %w(7.1.1503 7.2.1511) - }, 'redhat' => { - 'versions' => %w(7.1 7.2) + 'versions' => %w(7.3) + }, + 'ubuntu' => { + 'versions' => %w(16.04) } } - platforms.each do |platform, components| components['versions'].each do |version| context "On #{platform} #{version}" do - before do - Fauxhai.mock(platform: platform, version: version) - end - - let(:chef_run) { ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) } + context 'When all attributes are default' do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['hashicorp']['bag_item'] = 'windows' + node.normal['hashicorp']['token'] = '123456789' + node.normal['hashicorp']['address'] = 'http://192.168.0.1:8200' + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } - it 'converges successfully' do - expect { chef_run }.to_not raise_error + it 'converges successfully' do + expect(chef_run).to include_recipe('secrets_management_test::hashivault') + expect(chef_run).to include_recipe('secrets_management_test::chef_vault') + expect(chef_run).to include_recipe('secrets_management_test::data_bag') + expect { chef_run }.to_not raise_error + end end end end end end -end \ No newline at end of file +end diff --git a/spec/unit/recipes/hashivault_spec.rb b/spec/unit/recipes/hashivault_spec.rb new file mode 100644 index 0000000..32951d6 --- /dev/null +++ b/spec/unit/recipes/hashivault_spec.rb @@ -0,0 +1,72 @@ +# +# Cookbook:: secrets_management +# Spec:: default_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'secrets_management_test::hashivault' do + before do + # Hashivault stubs using webmocks + response_headers = { 'Content-Type' => 'application/json' } + vault_found = { request_id: '12345', lease_id: '', renewable: false, lease_duration: 2764800, data: { demo: true, test_key: '42' }, wrap_info: nil, warnings: nil, auth: nil } + + stub_request(:get, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_1').to_return(status: 200, body: vault_found.to_json, headers: response_headers) + stub_request(:put, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_1').with(body: '{"demo":true,"test_key":"42"}').to_return(status: 204, body: '', headers: response_headers) + end + context 'Validate supported installations' do + platforms = { + 'redhat' => { + 'versions' => %w(7.3) + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + context 'When all attributes are default' do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['hashicorp']['bag_item'] = 'windows' + node.normal['hashicorp']['token'] = '123456789' + node.normal['hashicorp']['address'] = 'http://192.168.0.1:8200' + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + expect(chef_run).to include_recipe('secrets_management_test::hashivault') + expect(chef_run).to write_log('Test a single hashicorp vault item') + expect(chef_run).to create_file('/tmp/cache/hashivault.test').with(content: '42') + end + + it 'raises an error if no token' do + node.normal['hashicorp']['token'] = nil + expect { chef_run }.to raise_error(ArgumentError, 'You did not provide details for the Hashicorp Vault server.') + end + + it 'raises an Vault::HTTPClientError exception when permission denied' do + stub_request(:get, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_1').to_return(status: 403, body: 'permission denied', headers: { 'Content-Type' => 'application/json' }) + expect { chef_run }.to raise_error(Vault::HTTPClientError, /permission denied/) + end + + it 'raises an Vault::HTTPServerError exception when server not responding' do + stub_request(:get, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_1').to_return(status: 503, body: 'connection refused: 192.168.100.78:8200', headers: { 'Content-Type' => 'application/json' }) + expect { chef_run }.to raise_error(Vault::HTTPServerError, /connection refused/) + end + end + end + end + end + end +end diff --git a/spec/unit/recipes/hashivault_with_chef_vault_spec.rb b/spec/unit/recipes/hashivault_with_chef_vault_spec.rb new file mode 100644 index 0000000..3f4d891 --- /dev/null +++ b/spec/unit/recipes/hashivault_with_chef_vault_spec.rb @@ -0,0 +1,75 @@ +# +# Cookbook:: secrets_management +# Spec:: hashivault_with_chef_vault_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'secrets_management_test::hashivault_with_chef_vault' do + before do + # Hashivault stubs using webmocks + response_headers = { 'Content-Type' => 'application/json' } + vault_found = { request_id: '12345', lease_id: '', renewable: false, lease_duration: 2764800, data: { demo: true, test_key: '84' }, wrap_info: nil, warnings: nil, auth: nil } + + stub_request(:get, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_2').to_return(status: 200, body: vault_found.to_json, headers: response_headers) + stub_request(:put, 'http://192.168.0.1:8200/v1//secret/chef/os/secrets_management_test_2').with(body: '{"demo":true,"test_key":"84"}').to_return(status: 204, body: '', headers: response_headers) + + # Need to perform the following in order to stub out the chef-vault items. + stub_request(:get, 'http://192.168.0.1:8200/v1/secrets/token').to_return(status: 404, body: {}.to_json, headers: response_headers) + allow(ChefVault::Item).to( + receive(:vault?).with('secrets', 'token').and_return(true) + ) + allow(ChefVault::Item) + .to receive(:load).with('secrets', 'token').and_return('id' => 'token', + 'token' => 'my_super_secret', + 'address' => 'http://192.168.0.1:8200') + allow(Chef::DataBag) + .to receive(:load).with('secrets').and_return('token_keys' => {}) + end + context 'Validate supported installations' do + platforms = { + 'redhat' => { + 'versions' => %w(7.3) + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + context 'When all attributes are default' do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['hashicorp']['token'] = '123456789' + node.normal['hashicorp']['address'] = 'http://192.168.0.1:8200' + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + expect(chef_run).to write_log('Test a single hashicorp vault item with ChefVault') + expect(chef_run).to create_file('/tmp/cache/hashivault_chef_vault.test').with(content: '84') + end + + it 'raises an error if the chef_vault item does not exist' do + allow(ChefVault::Item).to( + receive(:vault?).with('secrets', 'token').and_return(false) + ) + stub_data_bag_item('secrets', 'token').and_return(nil) + expect { chef_run }.to raise_error(ArgumentError, 'Failure: Unable to determine the bag_type or retrieve the bag_item (\'secrets\',\'token\'). This item might not exist.') + end + end + end + end + end + end +end diff --git a/tasks/maintainers.rb b/tasks/maintainers.rb index eed0c6d..f5c9e7b 100644 --- a/tasks/maintainers.rb +++ b/tasks/maintainers.rb @@ -39,7 +39,6 @@ end end end - rescue LoadError STDERR.puts "\n*** TomlRb not available. Skipping the Maintainers Rake task\n\n" end diff --git a/test/fixtures/clients/exospheredemo.json b/test/fixtures/clients/exospheredemo.json new file mode 100644 index 0000000..9b5ff46 --- /dev/null +++ b/test/fixtures/clients/exospheredemo.json @@ -0,0 +1,8 @@ +{ + "name": "exospheredemo", + "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAroTVtRfhrIp8HsDgWOQ6\nwPSR/trQhCJy0izY8cSHK2tlg0H6tH326qXn+cPtdyxKm5y21+z5/Ap2o4Y2Z/xa\nyMPbWUENPCnNgva8dAPQ71+IzbnQTN9SWL6yBEBtC0Ap1mDM8/H1w4xseIAU2pSf\n+ul0Ly8k5LEhyIYxoo+/7FyUJN0J28Tj+tFlSDJtWAuRryvdKRrIODg1tiaCuOkm\nOSM2djB/oyy5xtMky/4iZCaKZO6C37Z1qGOKrU7cZf3eDJxjUjp3MWrWniCOjiha\nbI86ToKsN/Dh4MSXUjvSp7PQRMEZoVNcVb1K6BSNq4FAjadFhR6wfLTV0gYCz5gE\nuwIDAQAB\n-----END PUBLIC KEY-----\n", + "validator": false, + "admin": true, + "json_class": "Chef::ApiClient", + "chef_type": "client" +} diff --git a/test/fixtures/clients/exospheredemo.pem b/test/fixtures/clients/exospheredemo.pem new file mode 100644 index 0000000..85c975c --- /dev/null +++ b/test/fixtures/clients/exospheredemo.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAroTVtRfhrIp8HsDgWOQ6wPSR/trQhCJy0izY8cSHK2tlg0H6 +tH326qXn+cPtdyxKm5y21+z5/Ap2o4Y2Z/xayMPbWUENPCnNgva8dAPQ71+IzbnQ +TN9SWL6yBEBtC0Ap1mDM8/H1w4xseIAU2pSf+ul0Ly8k5LEhyIYxoo+/7FyUJN0J +28Tj+tFlSDJtWAuRryvdKRrIODg1tiaCuOkmOSM2djB/oyy5xtMky/4iZCaKZO6C +37Z1qGOKrU7cZf3eDJxjUjp3MWrWniCOjihabI86ToKsN/Dh4MSXUjvSp7PQRMEZ +oVNcVb1K6BSNq4FAjadFhR6wfLTV0gYCz5gEuwIDAQABAoIBAQCteD5Qxo88iV78 +1q1pzlqUZ8Yi9G+ll/RX89ok3zuYriT2RoQkGr1v2j9uZhFJjw4OBcU9dkG6BNCO +lJ7J1+6Jdx02Z9H3BnpP/l+uVgi9l1GNZHCnqKfarJqYXU3GjB3KCOhQZybqHE/J +q06PQoKOzrT9HbhnVaaGAtWdh5O3JV6sIfQLv2MLkqMii4Si9YHy/gg3VEesQMFO +W0pVENxOLzEuSY+yY3cGvpw1cidRRfzoy9VBj8kl1mm6kOUfHMxL/n6hmhTC6Rkz +NPvJMM0YL04Nawo7IP0Ymwl6US4kBj3RB7UOOwCo7A7V6i+/MIXFjF25PTZkTnxI +WGV6KN2RAoGBANNl1gKAeu/lWIn8OyrgcAq4b6Qbpq+77KWQX8Lwd1DqwVSsZjcm +maGvsz3C0Vv/m4CXjb5Q7clRUnQ1Idmu9VS06BeBEZR6X2GP875tOtIGY72S3KaV +N8FdC+IO1f7f65G261Nxj6fAHU78i76U0ZwPyn8gJBv04S1Qp+sB8VYTAoGBANNX +Ec+7McAiw5RMyuAmMmetNFxGKag1ZkyiBOJ7W0xXErnoFgzRBZJqtJNRmZiv8376 +ekKj8h5BUiTGKBl8/IF20USrFrp6SFFgGGPIbRTr6GWAQsKapoO3au/J46J0249d +nzxgRBrOoMZgXSNiuBiB5NN7dyooAa6R2LY4Sgu5AoGAeqNlmGX47BtdVg+iM6mo +1k9RVR9FgEXBb/tl0q22SpcmOX0af3xBPG+1yGqxEnIjQGKrtHE2wcgP0aLRVHwP +j2i0DI6wKDcmBm+AtESiDx3t1xjQMUP36JGWRzrcItS80PgRZYNIF8V4O05/s7ya +LbNrgnye9ikGSUe1YrzpyUkCgYBmmgnaqQ3RMs9yywlfYwvri4o1FhcZQ7uDjtEC +R1Mer9M2kDdMR5oF6Gn8b4w7NYjoZ6A9hCyChcHkepNzNFu+WrnkNY+WPtGnOBj/ +MInMs28i/+7pXX1jywNOoNwzvbnuU0yAWlgWD6S8SoJNMJshwsYd6f4DaGLQppvo +c1F3eQKBgQCk0cIYRT9iQTjcO/Mw+WVmQvItclWyDMukkIEBNR/5b2nlzsPBvstD +IoSBKidXpZrywerzcmL2EouGn2p5fvHHleHacwSh5ilsvMeI8tKjnDT8egyAMoer +FwJiCNhUEumaQGET5mjxBYcsXLIQ6ED6hVi8ZrROgVzIPDdy28fVeA== +-----END RSA PRIVATE KEY----- + diff --git a/test/fixtures/cookbooks/test/README.md b/test/fixtures/cookbooks/test/README.md new file mode 100644 index 0000000..3028329 --- /dev/null +++ b/test/fixtures/cookbooks/test/README.md @@ -0,0 +1 @@ +This cookbook is used for testing the secrets_management cookbook diff --git a/test/fixtures/cookbooks/test/metadata.rb b/test/fixtures/cookbooks/test/metadata.rb new file mode 100644 index 0000000..6bfdc1d --- /dev/null +++ b/test/fixtures/cookbooks/test/metadata.rb @@ -0,0 +1,9 @@ +name 'secrets_management_test' +maintainer 'Exosphere Data, LLC' +maintainer_email 'chef@exospheredata.com' +license 'MIT' +description 'A Test cookbook for the secrets_management resources' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'secrets_management' diff --git a/test/fixtures/cookbooks/test/recipes/chef_vault.rb b/test/fixtures/cookbooks/test/recipes/chef_vault.rb new file mode 100644 index 0000000..ed2bbac --- /dev/null +++ b/test/fixtures/cookbooks/test/recipes/chef_vault.rb @@ -0,0 +1,23 @@ +# +# Cookbook Name:: secrets_management_test +# Recipe:: chef_vault +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +bag = open_secret_item('secrets', 'bacon') + +log 'Test a single chef_vault item' do + message "We found the following details in the test bag: #{bag}" + level :info +end + +bag = open_secret_item('secrets', 'bacon', bag_type: 'chef_vault') + +log 'Test a direct check chef_vault item' do + message "We found the following details in the test bag: #{bag}" + level :info +end + +file "#{Chef::Config[:file_cache_path]}/chef_vault.test" do + content bag['password'] +end diff --git a/test/fixtures/cookbooks/test/recipes/data_bag.rb b/test/fixtures/cookbooks/test/recipes/data_bag.rb new file mode 100644 index 0000000..cb4d93b --- /dev/null +++ b/test/fixtures/cookbooks/test/recipes/data_bag.rb @@ -0,0 +1,22 @@ +# +# Cookbook Name:: secrets_management_test +# Recipe:: data_bag +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +# By disabling this attribute, we can override the fallback included +# in the chef-vault cookbook at properly test the data_bag lookup +node.normal['chef-vault']['databag_fallback'] = false + +bag = open_secret_item('simple', 'item', bag_type: 'data_bag') + +node.normal['chef-vault']['databag_fallback'] = true + +log 'Test a direct check data_bag item' do + message "We found the following details in the test bag: #{bag}" + level :info +end + +file "#{Chef::Config[:file_cache_path]}/data_bag.test" do + content bag['test_key'] +end diff --git a/test/fixtures/cookbooks/test/recipes/default.rb b/test/fixtures/cookbooks/test/recipes/default.rb new file mode 100644 index 0000000..95ad16c --- /dev/null +++ b/test/fixtures/cookbooks/test/recipes/default.rb @@ -0,0 +1,9 @@ +# +# Cookbook Name:: secrets_management_test +# Recipe:: default +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +include_recipe 'secrets_management_test::hashivault' +include_recipe 'secrets_management_test::chef_vault' +include_recipe 'secrets_management_test::data_bag' diff --git a/test/fixtures/cookbooks/test/recipes/hashivault.rb b/test/fixtures/cookbooks/test/recipes/hashivault.rb new file mode 100644 index 0000000..20a6fc1 --- /dev/null +++ b/test/fixtures/cookbooks/test/recipes/hashivault.rb @@ -0,0 +1,25 @@ +# +# Cookbook Name:: secrets_management_test +# Recipe:: hashivault +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +vault_hash = {} +# By default, the keys need to be symbolized. The DSL contains a method to convert to symbol +# if a string is passed, so we will test both here. +vault_hash[:token] = node['hashicorp']['token'] +vault_hash[:address] = node['hashicorp']['address'] + +vault = Vault::Client.new(vault_hash) +vault.logical.write('/secret/chef/os/secrets_management_test_1', demo: true, test_key: '42') + +bag = open_secret_item('/secret/chef/os', 'secrets_management_test_1', vault: vault_hash) + +log 'Test a single hashicorp vault item' do + message "We found the following details in the test bag: #{bag}" + level :info +end + +file "#{Chef::Config[:file_cache_path]}/hashivault.test" do + content bag['test_key'] +end diff --git a/test/fixtures/cookbooks/test/recipes/hashivault_with_chef_vault.rb b/test/fixtures/cookbooks/test/recipes/hashivault_with_chef_vault.rb new file mode 100644 index 0000000..630c391 --- /dev/null +++ b/test/fixtures/cookbooks/test/recipes/hashivault_with_chef_vault.rb @@ -0,0 +1,28 @@ +# +# Cookbook Name:: secrets_management_test +# Recipe:: hashivault_with_chef_vault +# +# Copyright:: 2017, Exosphere Data, LLC, All Rights Reserved. + +vault_hash = {} +# By default, the keys need to be symbolized. The DSL contains a method to convert to symbol +# if a string is passed, so we will test both here. +vault_hash[:token] = node['hashicorp']['token'] +vault_hash[:address] = node['hashicorp']['address'] + +# Test using a ChefVault item to load the hash details. +chef_vault_hash = open_secret_item('secrets', 'token', vault: vault_hash) + +vault = Vault::Client.new(vault_hash) +vault.logical.write('/secret/chef/os/secrets_management_test_2', demo: true, test_key: '84') + +bag = open_secret_item('/secret/chef/os', 'secrets_management_test_2', bag_type: 'vault', vault: chef_vault_hash) + +log 'Test a single hashicorp vault item with ChefVault' do + message "We found the following details in the test bag: #{bag}" + level :info +end + +file "#{Chef::Config[:file_cache_path]}/hashivault_chef_vault.test" do + content bag['test_key'] +end diff --git a/test/fixtures/data_bags/secrets/bacon.json b/test/fixtures/data_bags/secrets/bacon.json new file mode 100644 index 0000000..c6f43d0 --- /dev/null +++ b/test/fixtures/data_bags/secrets/bacon.json @@ -0,0 +1,4 @@ +{ + "id": "bacon", + "password": "my_secret_token" +} diff --git a/test/fixtures/data_bags/secrets/dbpassword.json b/test/fixtures/data_bags/secrets/dbpassword.json new file mode 100644 index 0000000..568a724 --- /dev/null +++ b/test/fixtures/data_bags/secrets/dbpassword.json @@ -0,0 +1,4 @@ +{ + "id": "dbpassword", + "auth": "success" +} diff --git a/test/fixtures/data_bags/simple/item.json b/test/fixtures/data_bags/simple/item.json new file mode 100644 index 0000000..9349b19 --- /dev/null +++ b/test/fixtures/data_bags/simple/item.json @@ -0,0 +1,4 @@ +{ + "id": "item", + "test_key": "value1" +} diff --git a/test/fixtures/environments/test.json b/test/fixtures/environments/test.json new file mode 100644 index 0000000..0fbad82 --- /dev/null +++ b/test/fixtures/environments/test.json @@ -0,0 +1,3 @@ +{ + "id": "test" +} diff --git a/test/fixtures/nodes/exospheredemo.json b/test/fixtures/nodes/exospheredemo.json new file mode 100644 index 0000000..0825556 --- /dev/null +++ b/test/fixtures/nodes/exospheredemo.json @@ -0,0 +1,9 @@ +{ + "name": "exospheredemo", + "chef_environment": "test", + "run_list": [ + + ], + "normal": { + } +} diff --git a/test/smoke/default/default_test.rb b/test/smoke/default/default_test.rb deleted file mode 100644 index d97bfad..0000000 --- a/test/smoke/default/default_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -# # encoding: utf-8 - -# Inspec test for recipe secrets_management::default - -# The Inspec reference, with examples and extensive documentation, can be -# found at http://inspec.io/docs/reference/resources/ - -unless os.windows? - describe user('root') do - it { should exist } - skip 'This is an example test, replace with your own test.' - end -end - -describe port(80) do - it { should_not be_listening } - skip 'This is an example test, replace with your own test.' -end diff --git a/test/smoke/secrets_management_tests/README.md b/test/smoke/secrets_management_tests/README.md new file mode 100644 index 0000000..8e52bbc --- /dev/null +++ b/test/smoke/secrets_management_tests/README.md @@ -0,0 +1,3 @@ +# Example InSpec Profile + +This example shows the implementation of an InSpec [profile](../../docs/profiles.rst). diff --git a/test/smoke/secrets_management_tests/controls/example.rb b/test/smoke/secrets_management_tests/controls/example.rb new file mode 100644 index 0000000..f917fb3 --- /dev/null +++ b/test/smoke/secrets_management_tests/controls/example.rb @@ -0,0 +1,25 @@ +# encoding: utf-8 +# copyright: 2015, The Authors + +title 'Validate Output from Secrets Management' + +control 'Verify all files created' do + impact 1.0 + title 'Verify yum package package @ is installed' + desc 'Check to see if the host properly installed the yum package ' + + describe file('/tmp/kitchen/cache/data_bag.test') do + it { should be_file } + its('content') { should eq 'value1' } + end + + describe file('/tmp/kitchen/cache/chef_vault.test') do + it { should be_file } + its('content') { should eq 'my_secret_token' } + end + + describe file('/tmp/kitchen/cache/hashivault.test') do + it { should be_file } + its('content') { should eq '42' } + end +end diff --git a/test/smoke/secrets_management_tests/inspec.yml b/test/smoke/secrets_management_tests/inspec.yml new file mode 100644 index 0000000..d2d8530 --- /dev/null +++ b/test/smoke/secrets_management_tests/inspec.yml @@ -0,0 +1,8 @@ +name: secrets_management_tests +title: InSpec Profile +maintainer: 'Exosphere Data, LLC' +copyright: 'Exosphere Data, LLC' +copyright_email: chef@exospheredata.com +license: MIT +summary: An InSpec Compliance Profile +version: 0.1.0 diff --git a/test/smoke/secrets_management_tests/libraries/.gitkeep b/test/smoke/secrets_management_tests/libraries/.gitkeep new file mode 100644 index 0000000..e69de29