summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCollin McNeese <cmcneese@chef.io>2021-11-20 09:51:54 -0600
committerCollin McNeese <cmcneese@chef.io>2021-12-14 15:34:59 -0600
commitc4c13058303616d52ffa8f69cb7a93f4fcc59c6b (patch)
tree490c928f951f9d3e55965abe5531074ecd1273d4
parent8d42cb97966db0ec046e42caf09d2a0b39bebee3 (diff)
downloadchef-c4c13058303616d52ffa8f69cb7a93f4fcc59c6b.tar.gz
Adds approle support for Chef::SecretFetcher::HashiVault
Signed-off-by: Collin McNeese <cmcneese@chef.io>
-rw-r--r--cspell.json1
-rw-r--r--lib/chef/secret_fetcher/hashi_vault.rb40
-rw-r--r--spec/unit/secret_fetcher/hashi_vault_spec.rb47
3 files changed, 85 insertions, 3 deletions
diff --git a/cspell.json b/cspell.json
index 6f40be96c2..838d70c661 100644
--- a/cspell.json
+++ b/cspell.json
@@ -46,6 +46,7 @@
"anonymized",
"APISSL",
"applewood",
+ "approle",
"Appscript",
"appscript",
"ARCHITEW",
diff --git a/lib/chef/secret_fetcher/hashi_vault.rb b/lib/chef/secret_fetcher/hashi_vault.rb
index 47bf78f5c1..170fcba4b6 100644
--- a/lib/chef/secret_fetcher/hashi_vault.rb
+++ b/lib/chef/secret_fetcher/hashi_vault.rb
@@ -31,6 +31,10 @@ class Chef
# :auth_method - one of :iam_role, :token. default: :iam_role
# :vault_addr - the address of a running Vault instance, eg https://vault.example.com:8200
#
+ # For `:approle`: one of `:approle_name` or `:approle_id`
+ # `:approle_name`: The name of the approle to use for authentication. When specified, associated `:approle_id` will be found via query to Vault instance.
+ # `:approle_id`: The ID of the approle to use for authentication, requires `:approle_secret_id`
+ # `:approle_secret_id`: The Vault `secret_id` associated with the provided `:approle_name` or `:approle_id`. When specified, prevents need to create `:secret_id` with `:approle_name`.
# For `:token` auth: `:token` - a Vault token valid for authentication.
#
# For `:iam_role`: `:role_name` - the name of the role in Vault that was created
@@ -47,14 +51,25 @@ class Chef
#
# @example
#
- # fetcher = SecretFetcher.for_service(:hashi_vault, { role_name: "testing-role", vault_addr: https://localhost:8200}, run_context )
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { auth_method: :iam_role, role_name: "testing-role", vault_addr: https://localhost:8200}, run_context )
# fetcher.fetch("secretkey1")
#
# @example
#
- # fetcher = SecretFetcher.for_service(:hashi_vault, { auth_method: :token, token: "s.1234abcdef", vault_addr: https://localhost:8200}, run_context )
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { auth_method: :token, token: "s.1234abcdef", vault_addr: https://localhost:8200}, approle: 'approle_name', run_context )
# fetcher.fetch("secretkey1")
- SUPPORTED_AUTH_TYPES = %i{iam_role token}.freeze
+ #
+ # @example
+ #
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { auth_method: :approle, approle_id: "11111111-abcd-1111-abcd-111111111111", approle_secret_id: "22222222-abcd-2222-abcd-222222222222", vault_addr: https://localhost:8200}, run_context )
+ # fetcher.fetch("secretkey1")
+ #
+ # @example
+ #
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { auth_method: :approle, approle_name: "testing-role", token: "s.1234abcdef", vault_addr: https://localhost:8200}, run_context )
+ # fetcher.fetch("secretkey1")
+ #
+ SUPPORTED_AUTH_TYPES = %i{approle iam_role token}.freeze
class HashiVault < Base
# Validate and authenticate the current session using the configured auth strategy and parameters
@@ -67,6 +82,25 @@ class Chef
Vault.namespace = config[:namespace] unless config[:namespace].nil?
case config[:auth_method]
+ when :approle
+ unless config[:approle_name] || config[:approle_id]
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the :approle_name or :approle_id in the configuration with :auth_method set to :approle")
+ end
+
+ # When :approle_id and :approle_secret_id are both specified, all pieces are present which are needed to authenticate using an approle.
+ # If either is missing, we need to authenticate to Vault to get the missing pieces with the :approle_name and optionally :token.
+ unless config[:approle_id] && config[:approle_secret_id]
+ if config[:approle_name].nil?
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the :approle_name in the configuration when :approle_id and :approle_secret_id are not both present with :auth_method set to :approle")
+ end
+
+ Vault.token = config[:token] unless config[:token].nil?
+ end
+
+ approle_id = config[:approle_id] || Vault.approle.role_id(config[:approle_name])
+ approle_secret_id = config[:approle_secret_id] || Vault.approle.create_secret_id(config[:approle_name]).data[:secret_id]
+
+ Vault.auth.approle(approle_id, approle_secret_id)
when :token
if config[:token].nil?
raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the token in the configuration as :token")
diff --git a/spec/unit/secret_fetcher/hashi_vault_spec.rb b/spec/unit/secret_fetcher/hashi_vault_spec.rb
index e69c397c17..7d7acc424a 100644
--- a/spec/unit/secret_fetcher/hashi_vault_spec.rb
+++ b/spec/unit/secret_fetcher/hashi_vault_spec.rb
@@ -65,6 +65,53 @@ describe Chef::SecretFetcher::HashiVault do
fetcher.validate!
end
end
+
+ context "and using auth_method: :approle" do
+ it "raises ConfigurationInvalid message when :approle_name or :approle_id are not specified" do
+ fetcher = Chef::SecretFetcher::HashiVault.new( { auth_method: :approle, vault_addr: "https://vault.example.com:8200" }, run_context)
+ expect { fetcher.validate! }.to raise_error(Chef::Exceptions::Secret::ConfigurationInvalid)
+ expect { fetcher.validate! }.to raise_error("You must provide the :approle_name or :approle_id in the configuration with :auth_method set to :approle")
+ end
+
+ it "authenticates using the approle_id and approle_secret_id during validation when all configuration is correct" do
+ fetcher = Chef::SecretFetcher::HashiVault.new({
+ auth_method: :approle,
+ approle_id: "idguid",
+ approle_secret_id: "secretguid",
+ vault_addr: "https://vault.example.com:8200" },
+ run_context)
+ auth = instance_double(Vault::Authenticate)
+ allow(auth).to receive(:approle)
+ allow(Vault).to receive(:auth).and_return(auth)
+ expect(auth).to receive(:approle).with("idguid", "secretguid")
+ fetcher.validate!
+ end
+
+ it "looks up the :role_id and :secret_id when all configuration is correct" do
+ fetcher = Chef::SecretFetcher::HashiVault.new({
+ auth_method: :approle,
+ approle_name: "myapprole",
+ token: "t.1234abcd",
+ vault_addr: "https://vault.example.com:8200" },
+ run_context)
+ approle = instance_double(Vault::AppRole)
+ auth = instance_double(Vault::Authenticate)
+ allow(Vault).to receive(:approle).and_return(approle)
+ allow(approle).to receive(:role_id).with("myapprole").and_return("idguid")
+ allow(approle).to receive(:create_secret_id).with("myapprole").and_return(Vault::Secret.new({
+ data: {
+ secret_id: "secretguid",
+ secret_id_accessor: "accessor_guid",
+ secret_id_ttl: 0,
+ },
+ lease_duration: 0,
+ lease_id: "",
+ }))
+ allow(Vault).to receive(:auth).and_return(auth)
+ expect(auth).to receive(:approle).with("idguid", "secretguid")
+ fetcher.validate!
+ end
+ end
end
context "when fetching a secret from Hashi Vault" do