diff options
author | Tim Smith <tsmith84@gmail.com> | 2021-12-15 07:34:31 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-15 07:34:31 -0800 |
commit | 4a572cebb3231b9f16ab7e5fd4fdfdc3ac7ac05e (patch) | |
tree | a3c7210cdc303e62924c505d7235d57d2a204f0d | |
parent | ae7709269bf00749a1e258b2ff7898bef4eb02ad (diff) | |
parent | 44e7d8ac2eea3135b127907e908dedc6c350296e (diff) | |
download | chef-4a572cebb3231b9f16ab7e5fd4fdfdc3ac7ac05e.tar.gz |
Merge pull request #12300 from collinmcneese/cmcneese/secrets_hashi_approle
-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 | 46 |
3 files changed, 84 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..7a6b7aae70 100644 --- a/spec/unit/secret_fetcher/hashi_vault_spec.rb +++ b/spec/unit/secret_fetcher/hashi_vault_spec.rb @@ -65,6 +65,52 @@ 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) + 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 |