summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2021-07-28 10:17:45 -0700
committerGitHub <noreply@github.com>2021-07-28 10:17:45 -0700
commit0e041b5a6f75cf9de31d5c926a9f9b4528d2c86b (patch)
tree4e622f0d86628a2dc8fcd028408d0d1c1fdf951c
parent38660cb3665b9aa6ed319ca54a4adadd2dee39b9 (diff)
parent84aa9bdc504026489946c3bfec8bc81e91c720f1 (diff)
downloadchef-0e041b5a6f75cf9de31d5c926a9f9b4528d2c86b.tar.gz
Merge pull request #11867 from chef/mp/11852
Allow az vault name to be included in secret name
-rw-r--r--lib/chef/exceptions.rb2
-rw-r--r--lib/chef/secret_fetcher/azure_key_vault.rb40
-rw-r--r--spec/unit/secret_fetcher/azure_key_vault_spec.rb37
3 files changed, 53 insertions, 26 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index 8e9bced8bb..249a90e34b 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -308,8 +308,6 @@ class Chef
super("No secret service provided. Supported services are: :#{fetcher_service_names.join(" :")}")
end
end
-
- class MissingVaultName < RuntimeError; end
end
# Exception class for collecting multiple failures. Used when running
diff --git a/lib/chef/secret_fetcher/azure_key_vault.rb b/lib/chef/secret_fetcher/azure_key_vault.rb
index 734bd94df1..1d2bc2af04 100644
--- a/lib/chef/secret_fetcher/azure_key_vault.rb
+++ b/lib/chef/secret_fetcher/azure_key_vault.rb
@@ -8,23 +8,30 @@ class Chef
# In this initial iteration this authenticates via token obtained from the OAuth2 /token
# endpoint.
#
- # Usage Example:
+ # Validation of required configuration (vault name) is not performed until
+ # `fetch` time, to allow for embedding the vault name in with the secret
+ # name, such as "my_vault/secretkey1".
#
- # fetcher = SecretFetcher.for_service(:azure_key_vault)
+ # @example
+ #
+ # fetcher = SecretFetcher.for_service(:azure_key_vault, { vault: "my_vault" }, run_context )
# fetcher.fetch("secretkey1", "v1")
+ #
+ # @example
+ #
+ # fetcher = SecretFetcher.for_service(:azure_key_vault, {}, run_context )
+ # fetcher.fetch("my_vault/secretkey1", "v1")
class AzureKeyVault < Base
- def validate!
- @vault = config[:vault]
- if @vault.nil?
- raise Chef::Exceptions::Secret::MissingVaultName.new("You must provide a vault name to service options as vault: 'vault_name'")
- end
- end
def do_fetch(name, version)
token = fetch_token
+ vault, name = resolve_vault_and_secret_name(name)
+ if vault.nil?
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide a vault name to fetcher options as vault: 'vault_name' or in the secret name as 'vault_name/secret_name'")
+ end
# Note that `version` is optional after the final `/`. If nil/"", the latest secret version will be fetched.
- secret_uri = URI.parse("https://#{@vault}.vault.azure.net/secrets/#{name}/#{version}?api-version=7.2")
+ secret_uri = URI.parse("https://#{vault}.vault.azure.net/secrets/#{name}/#{version}?api-version=7.2")
http = Net::HTTP.new(secret_uri.host, secret_uri.port)
http.use_ssl = true
@@ -41,6 +48,21 @@ class Chef
end
end
+ # Determine the vault name and secret name from the provided name.
+ # If it is not in the provided name in the form "vault_name/secret_name"
+ # it will determine the vault name from `config[:vault]`.
+ # @param name [String] the secret name or vault and secret name in the form "vault_name/secret_name"
+ # @return Array[String, String] vault and secret name respectively
+ def resolve_vault_and_secret_name(name)
+ # We support a simplified approach where the vault name is not passed i
+ # into configuration, but
+ if name.include?("/")
+ name.split("/", 2)
+ else
+ [config[:vault], name]
+ end
+ end
+
def fetch_token
token_uri = URI.parse("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net")
http = Net::HTTP.new(token_uri.host, token_uri.port)
diff --git a/spec/unit/secret_fetcher/azure_key_vault_spec.rb b/spec/unit/secret_fetcher/azure_key_vault_spec.rb
index d41973992b..d0fc2727bc 100644
--- a/spec/unit/secret_fetcher/azure_key_vault_spec.rb
+++ b/spec/unit/secret_fetcher/azure_key_vault_spec.rb
@@ -22,20 +22,11 @@ require "chef/secret_fetcher"
require "chef/secret_fetcher/azure_key_vault"
describe Chef::SecretFetcher::AzureKeyVault do
- let(:config) { { vault: "myvault" } }
+ let(:config) { { vault: "my_vault" } }
let(:fetcher) { Chef::SecretFetcher::AzureKeyVault.new(config, nil) }
- context "when validating configuration and configuration is missing :vault" do
- context "and configuration does not have a 'vault'" do
- let(:config) { {} }
- it "raises a MissingVaultError error on validate!" do
- expect { fetcher.validate! }.to raise_error(Chef::Exceptions::Secret::MissingVaultName)
- end
- end
- end
-
context "when performing a fetch" do
- let(:body) { "" }
+ let(:body) { '{ "value" : "my secret value" }' }
let(:response_mock) { double("response", body: body) }
let(:http_mock) { double("http", :get => response_mock, :use_ssl= => nil) }
@@ -44,20 +35,36 @@ describe Chef::SecretFetcher::AzureKeyVault do
allow(Net::HTTP).to receive(:new).and_return(http_mock)
end
- context "and a valid response is received" do
+ context "and vault name is only provided in the secret name" do
let(:body) { '{ "value" : "my secret value" }' }
- it "returns the expected response" do
- expect(fetcher.fetch("value")).to eq "my secret value"
+ let(:config) { {} }
+ it "fetches the value" do
+ expect(fetcher.fetch("my_vault/value")).to eq "my secret value"
end
end
+ context "and vault name is not provided in the secret name" do
+ context "and vault name is not provided in config" do
+ let(:config) { {} }
+ it "raises a ConfigurationInvalid exception" do
+ expect { fetcher.fetch("value") }.to raise_error(Chef::Exceptions::Secret::ConfigurationInvalid)
+ end
+ end
+
+ context "and vault name is provided in config" do
+ let(:config) { { vault: "my_vault" } }
+ it "fetches the value" do
+ expect(fetcher.fetch("value")).to eq "my secret value"
+ end
+ end
+ end
context "and an error response is received in the body" do
+ let(:config) { { vault: "my_vault" } }
let(:body) { '{ "error" : { "code" : 404, "message" : "secret not found" } }' }
it "raises FetchFailed" do
expect { fetcher.fetch("value") }.to raise_error(Chef::Exceptions::Secret::FetchFailed)
end
end
-
end
end