diff options
author | Collin McNeese <cmcneese@chef.io> | 2021-11-20 09:51:54 -0600 |
---|---|---|
committer | Collin McNeese <cmcneese@chef.io> | 2021-12-14 15:34:59 -0600 |
commit | c4c13058303616d52ffa8f69cb7a93f4fcc59c6b (patch) | |
tree | 490c928f951f9d3e55965abe5531074ecd1273d4 | |
parent | 8d42cb97966db0ec046e42caf09d2a0b39bebee3 (diff) | |
download | chef-c4c13058303616d52ffa8f69cb7a93f4fcc59c6b.tar.gz |
Adds approle support for Chef::SecretFetcher::HashiVault
Signed-off-by: Collin McNeese <cmcneese@chef.io>
-rw-r--r-- | cspell.json | 1 | ||||
-rw-r--r-- | lib/chef/secret_fetcher/hashi_vault.rb | 40 | ||||
-rw-r--r-- | spec/unit/secret_fetcher/hashi_vault_spec.rb | 47 |
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 |