summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc A. Paradise <marcparadise@users.noreply.github.com>2021-08-27 22:10:02 -0400
committerGitHub <noreply@github.com>2021-08-27 22:10:02 -0400
commit770aacafc47f3530a67f727d107352cf258388bb (patch)
treea19ba3d4e680db4b20433030fa73bbf18d3989a2
parentfd68f7f1fb1e63f1cb40d7ef24346afd0884ed95 (diff)
parentc4d953da87c39f0e5c235ce0579f04ba576d320b (diff)
downloadchef-770aacafc47f3530a67f727d107352cf258388bb.tar.gz
Merge pull request #11942 from chef/mp/11701
Add support for secrets stored in HashiCorp Vault
-rw-r--r--Gemfile.lock4
-rw-r--r--chef.gemspec1
-rw-r--r--lib/chef/secret_fetcher.rb5
-rw-r--r--lib/chef/secret_fetcher/aws_secrets_manager.rb2
-rw-r--r--lib/chef/secret_fetcher/azure_key_vault.rb2
-rw-r--r--lib/chef/secret_fetcher/hashi_vault.rb70
-rw-r--r--spec/unit/secret_fetcher/hashi_vault_spec.rb47
7 files changed, 128 insertions, 3 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 751ec66a11..eab86bc2a9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -63,6 +63,7 @@ PATH
train-core (~> 3.2, >= 3.2.28)
train-winrm (>= 0.2.5)
uuidtools (>= 2.1.5, < 3.0)
+ vault (~> 0.16)
chef (17.4.38-universal-mingw32)
addressable
aws-sdk-secretsmanager (~> 1.46)
@@ -92,6 +93,7 @@ PATH
train-core (~> 3.2, >= 3.2.28)
train-winrm (>= 0.2.5)
uuidtools (>= 2.1.5, < 3.0)
+ vault (~> 0.16)
win32-api (~> 1.5.3)
win32-certstore (~> 0.6.2)
win32-event (~> 0.6.1)
@@ -371,6 +373,8 @@ GEM
unicode-display_width (2.0.0)
unicode_utils (1.4.0)
uuidtools (2.2.0)
+ vault (0.16.0)
+ aws-sigv4
webmock (3.14.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
diff --git a/chef.gemspec b/chef.gemspec
index 0f278fff8f..936a9dfd91 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -56,6 +56,7 @@ Gem::Specification.new do |s|
s.add_dependency "proxifier", "~> 1.0"
s.add_dependency "aws-sdk-secretsmanager", "~> 1.46"
+ s.add_dependency "vault", "~> 0.16" # hashi vault official client gem
s.bindir = "bin"
s.executables = %w{ }
diff --git a/lib/chef/secret_fetcher.rb b/lib/chef/secret_fetcher.rb
index c72e693290..e8e4602bb2 100644
--- a/lib/chef/secret_fetcher.rb
+++ b/lib/chef/secret_fetcher.rb
@@ -21,7 +21,7 @@ require_relative "exceptions"
class Chef
class SecretFetcher
- SECRET_FETCHERS = %i{example aws_secrets_manager azure_key_vault}.freeze
+ SECRET_FETCHERS = %i{example aws_secrets_manager azure_key_vault hashi_vault}.freeze
# Returns a configured and validated instance
# of a [Chef::SecretFetcher::Base] for the given
@@ -42,6 +42,9 @@ class Chef
when :azure_key_vault
require_relative "secret_fetcher/azure_key_vault"
Chef::SecretFetcher::AzureKeyVault.new(config, run_context)
+ when :hashi_vault
+ require_relative "secret_fetcher/hashi_vault"
+ Chef::SecretFetcher::HashiVault.new(config, run_context)
when nil, ""
raise Chef::Exceptions::Secret::MissingFetcher.new(SECRET_FETCHERS)
else
diff --git a/lib/chef/secret_fetcher/aws_secrets_manager.rb b/lib/chef/secret_fetcher/aws_secrets_manager.rb
index c7b6b52b45..4ee7af41d4 100644
--- a/lib/chef/secret_fetcher/aws_secrets_manager.rb
+++ b/lib/chef/secret_fetcher/aws_secrets_manager.rb
@@ -52,7 +52,7 @@ class Chef
end
# @param identifier [String] the secret_id
- # @param version [String] the secret version. Not usd at this time
+ # @param version [String] the secret version.
# @return Aws::SecretsManager::Types::GetSecretValueResponse
def do_fetch(identifier, version)
client = Aws::SecretsManager::Client.new(config)
diff --git a/lib/chef/secret_fetcher/azure_key_vault.rb b/lib/chef/secret_fetcher/azure_key_vault.rb
index 1d2bc2af04..a617f3bb93 100644
--- a/lib/chef/secret_fetcher/azure_key_vault.rb
+++ b/lib/chef/secret_fetcher/azure_key_vault.rb
@@ -2,7 +2,7 @@ require_relative "base"
class Chef
class SecretFetcher
- # == Chef::SecretFetcher::AWSSecretsManager
+ # == Chef::SecretFetcher::AzureKeyVault
# A fetcher that fetches a secret from Azure Key Vault. Supports fetching with version.
#
# In this initial iteration this authenticates via token obtained from the OAuth2 /token
diff --git a/lib/chef/secret_fetcher/hashi_vault.rb b/lib/chef/secret_fetcher/hashi_vault.rb
new file mode 100644
index 0000000000..be975fc34f
--- /dev/null
+++ b/lib/chef/secret_fetcher/hashi_vault.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Marc Paradise (<marc@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "base"
+require "aws-sdk-core" # Support for aws instance profile auth
+require "vault"
+
+class Chef
+ class SecretFetcher
+ # == Chef::SecretFetcher::HashiVault
+ # A fetcher that fetches a secret from Hashi Vault.
+ #
+ # Does not yet support fetching with version when a versioned key store is in use.
+ # In this initial iteration the only supported authentication is IAM role-based
+ #
+ # Required config:
+ # :vault_addr - the address of a running Vault instance, eg https://vault.example.com:8200
+ # If not explicitly provided, the environment variable VAULT_ADDR will be used.
+ # :role_name - the name of the role in Vault that was created to support authentication
+ # via IAM. See the Vault documentation for details[1]. A Terraform example is also available[2]
+ #
+ # [1] https://www.vaultproject.io/docs/auth/aws#recommended-vault-iam-policy
+ # [2] https://registry.terraform.io/modules/hashicorp/vault/aws/latest/examples/vault-iam-auth
+ # an IAM principal ARN bound to it.
+ #
+ # @example
+ #
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { role_name: "testing-role", vault_addr: https://localhost:8200}, run_context )
+ # fetcher.fetch("secretkey1")
+ class HashiVault < Base
+ def validate!
+ if config[:role_name].nil?
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the authenticating Vault role name in the configuration as :role_name ")
+ end
+ if config[:vault_addr].nil?
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the Vault address in the configuration as :vault_addr")
+ end
+
+ # Note that the token here is cached internal to the Vault implementation.
+ Vault.auth.aws_iam(config[:role_name],
+ Aws::InstanceProfileCredentials.new,
+ config[:vault_addr] || ENV["VAULT_ADDR"])
+ end
+
+ # @param identifier [String] Identifier of the secret to be fetched, which should
+ # be the full path of that secret, eg 'secret/example'
+ # @param _version [String] not used in this implementation
+ # @return [Hash] containing key/value pairs stored at the location given in 'identifier'
+ def do_fetch(identifier, _version)
+ Vault.logical.read(identifier).data
+ end
+ end
+ end
+end
+
diff --git a/spec/unit/secret_fetcher/hashi_vault_spec.rb b/spec/unit/secret_fetcher/hashi_vault_spec.rb
new file mode 100644
index 0000000000..db93a051e4
--- /dev/null
+++ b/spec/unit/secret_fetcher/hashi_vault_spec.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Marc Paradise <marc@chef.io>
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+require_relative "../../spec_helper"
+require "chef/secret_fetcher/hashi_vault"
+
+describe Chef::SecretFetcher::HashiVault do
+ let(:node) { {} }
+ let(:run_context) { double("run_context", node: node) }
+
+ context "when validating HashiVault provided configuration" do
+ it "raises ConfigurationInvalid when the role_name is not provided" do
+ fetcher = Chef::SecretFetcher::HashiVault.new( { vault_addr: "vault.example.com" }, run_context)
+ expect { fetcher.validate! }.to raise_error(Chef::Exceptions::Secret::ConfigurationInvalid)
+ end
+
+ it "raises ConfigurationInvalid when the vault_addr is not provided" do
+ fetcher = Chef::SecretFetcher::HashiVault.new( { role_name: "vault.example.com" }, run_context)
+ expect { fetcher.validate! }.to raise_error(Chef::Exceptions::Secret::ConfigurationInvalid)
+ end
+
+ it "obtains a token via AWS IAM auth to allow the gem to do its own validations when all required config is provided" do
+ fetcher = Chef::SecretFetcher::HashiVault.new( { vault_addr: "vault.example.com", role_name: "example-role" }, run_context)
+ auth_stub =
+ allow(Aws::InstanceProfileCredentials).to receive(:new).and_return double("credentials")
+ allow(Vault).to receive(:auth).and_return(instance_double(Vault::Authenticate, aws_iam: nil))
+ fetcher.validate!
+ end
+ end
+end
+