summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2021-07-26 15:12:41 -0700
committerGitHub <noreply@github.com>2021-07-26 15:12:41 -0700
commit9029bb16b7aa5e95af2783ef94249ce05b01ecd9 (patch)
tree01c88810eb3112f126a1d5c4504352bc1e332949
parent193539ff80d7c7a4c52863a3ec5e29a417a4fd92 (diff)
parent1ce6fa2fae940a19a3604c10ef8316c0dc2568eb (diff)
downloadchef-9029bb16b7aa5e95af2783ef94249ce05b01ecd9.tar.gz
Merge pull request #11853 from chef/mp/11851
Signed-off-by: Tim Smith <tsmith@chef.io>
-rw-r--r--lib/chef/dsl/secret.rb6
-rw-r--r--lib/chef/secret_fetcher.rb9
-rw-r--r--lib/chef/secret_fetcher/aws_secrets_manager.rb20
-rw-r--r--lib/chef/secret_fetcher/base.rb6
-rw-r--r--spec/unit/dsl/secret_spec.rb10
-rw-r--r--spec/unit/secret_fetcher/aws_secrets_manager_spec.rb70
-rw-r--r--spec/unit/secret_fetcher/azure_key_vault_spec.rb2
-rw-r--r--spec/unit/secret_fetcher_spec.rb18
8 files changed, 117 insertions, 24 deletions
diff --git a/lib/chef/dsl/secret.rb b/lib/chef/dsl/secret.rb
index 8278ef0908..b02609c8bb 100644
--- a/lib/chef/dsl/secret.rb
+++ b/lib/chef/dsl/secret.rb
@@ -49,15 +49,15 @@ class Chef
#
# value = secret(name: "test1", service: :aws_secrets_manager, version: "v1", config: { region: "us-west-1" })
# log "My secret is #{value}"
- def secret(name: nil, version: nil, service: nil, config: nil)
- Chef::Log.warn <<~EOM.gsub("\n", "")
+ def secret(name: nil, version: nil, service: nil, config: {})
+ Chef::Log.warn <<~EOM.gsub("\n", " ")
The secrets Chef Infra language helper is currently in beta.
This helper will most likely change over time in potentially breaking ways.
If you have feedback or you'd like to be part of the future design of this
helper e-mail us at secrets_management_beta@progress.com"
EOM
sensitive(true) if is_a?(Chef::Resource)
- Chef::SecretFetcher.for_service(service, config).fetch(name, version)
+ Chef::SecretFetcher.for_service(service, config, run_context).fetch(name, version)
end
end
end
diff --git a/lib/chef/secret_fetcher.rb b/lib/chef/secret_fetcher.rb
index 9b8acd3790..c72e693290 100644
--- a/lib/chef/secret_fetcher.rb
+++ b/lib/chef/secret_fetcher.rb
@@ -30,17 +30,18 @@ class Chef
# @param service [Symbol] the identifier for the service that will support this request. Must be in
# SECRET_FETCHERS
# @param config [Hash] configuration that the secrets service requires
- def self.for_service(service, config)
+ # @param run_context [Chef::RunContext] the run context this is being invoked from
+ def self.for_service(service, config, run_context)
fetcher = case service
when :example
require_relative "secret_fetcher/example"
- Chef::SecretFetcher::Example.new(config)
+ Chef::SecretFetcher::Example.new(config, run_context)
when :aws_secrets_manager
require_relative "secret_fetcher/aws_secrets_manager"
- Chef::SecretFetcher::AWSSecretsManager.new(config)
+ Chef::SecretFetcher::AWSSecretsManager.new(config, run_context)
when :azure_key_vault
require_relative "secret_fetcher/azure_key_vault"
- Chef::SecretFetcher::AzureKeyVault.new(config)
+ Chef::SecretFetcher::AzureKeyVault.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 3619a37dd0..c7b6b52b45 100644
--- a/lib/chef/secret_fetcher/aws_secrets_manager.rb
+++ b/lib/chef/secret_fetcher/aws_secrets_manager.rb
@@ -17,6 +17,7 @@
#
require_relative "base"
+require "aws-sdk-core"
require "aws-sdk-secretsmanager"
class Chef
@@ -26,19 +27,30 @@ class Chef
# It is possible to pass options that configure it to use alternative credentials.
# This implementation supports fetching with version.
#
- # NOTE: This does not yet support automatic retries, which the AWS client does by default.
+ # @note ':region' is required configuration. If it is not explicitly provided,
+ # and it is not available via global AWS config, we will pull it from node ohai data by default.
+ # If this isn't correct, you will need to explicitly override it.
+ # If it is not available via ohai data either (such as if you have the AWS plugin disabled)
+ # then the converge will fail with an error.
+ #
+ # @note: This does not yet support automatic retries, which the AWS client does by default.
#
# For configuration options see https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SecretsManager/Client.html#initialize-instance_method
#
- # Note that ~/.aws default and environment-based configurations are supported by default in the
- # ruby SDK.
#
# Usage Example:
#
- # fetcher = SecretFetcher.for_service(:aws_secrets_manager, { region: "us-east-1" })
+ # fetcher = SecretFetcher.for_service(:aws_secrets_manager)
# fetcher.fetch("secretkey1", "v1")
class SecretFetcher
class AWSSecretsManager < Base
+ def validate!
+ config[:region] = config[:region] || Aws.config[:region] || run_context.node.dig("ec2", "region")
+ if config[:region].nil?
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("Missing required config for AWS secret fetcher: :region")
+ end
+ end
+
# @param identifier [String] the secret_id
# @param version [String] the secret version. Not usd at this time
# @return Aws::SecretsManager::Types::GetSecretValueResponse
diff --git a/lib/chef/secret_fetcher/base.rb b/lib/chef/secret_fetcher/base.rb
index 36b63990c0..2eee1df38b 100644
--- a/lib/chef/secret_fetcher/base.rb
+++ b/lib/chef/secret_fetcher/base.rb
@@ -25,13 +25,17 @@ class Chef
class SecretFetcher
class Base
attr_reader :config
+ # Note that this is only available in the context of a recipe.
+ # Since that's the only place it's intended to be used, that's probably OK.
+ attr_reader :run_context
# Initialize a new SecretFetcher::Base
#
# @param config [Hash] Configuration hash. Expected configuration keys and values
# will vary based on implementation, and are validated in `validate!`.
- def initialize(config)
+ def initialize(config, run_context)
@config = config
+ @run_context = run_context
end
# Fetch the named secret by invoking implementation-specific [Chef::SecretFetcher::Base#do_fetch]
diff --git a/spec/unit/dsl/secret_spec.rb b/spec/unit/dsl/secret_spec.rb
index ee25c511ee..96a915c43d 100644
--- a/spec/unit/dsl/secret_spec.rb
+++ b/spec/unit/dsl/secret_spec.rb
@@ -21,6 +21,12 @@ require "chef/dsl/secret"
require "chef/secret_fetcher/base"
class SecretDSLTester
include Chef::DSL::Secret
+ # Because DSL is invoked in the context of a recipe,
+ # we expect run_context to always be available when SecretFetcher::Base
+ # requests it - making it safe to mock here
+ def run_context
+ nil
+ end
end
class SecretFetcherImpl < Chef::SecretFetcher::Base
@@ -36,8 +42,8 @@ describe Chef::DSL::Secret do
end
it "uses SecretFetcher.for_service to find the fetcher" do
- substitute_fetcher = SecretFetcherImpl.new({})
- expect(Chef::SecretFetcher).to receive(:for_service).with(:example, {}).and_return(substitute_fetcher)
+ substitute_fetcher = SecretFetcherImpl.new({}, nil)
+ expect(Chef::SecretFetcher).to receive(:for_service).with(:example, {}, nil).and_return(substitute_fetcher)
expect(substitute_fetcher).to receive(:fetch).with("key1", nil)
dsl.secret(name: "key1", service: :example, config: {})
end
diff --git a/spec/unit/secret_fetcher/aws_secrets_manager_spec.rb b/spec/unit/secret_fetcher/aws_secrets_manager_spec.rb
new file mode 100644
index 0000000000..a98813a7bc
--- /dev/null
+++ b/spec/unit/secret_fetcher/aws_secrets_manager_spec.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 "../../spec_helper"
+require "chef/secret_fetcher/aws_secrets_manager"
+
+describe Chef::SecretFetcher::AWSSecretsManager do
+ let(:node) { {} }
+ let(:aws_global_config) { {} }
+ let(:fetcher_config) { {} }
+ let(:run_context) { double("run_context", node: node) }
+ let(:fetcher) {
+ Chef::SecretFetcher::AWSSecretsManager.new( fetcher_config, run_context )
+ }
+
+ before do
+ allow(Aws).to receive(:config).and_return(aws_global_config)
+ end
+
+ context "when region is provided" do
+ let(:fetcher_config) { { region: "region-from-caller" } }
+ it "uses the provided region" do
+ fetcher.validate!
+ expect(fetcher.config[:region]).to eq "region-from-caller"
+ end
+ end
+
+ context "when region is not provided" do
+ context "and no region exists in AWS config or node attributes" do
+ it "raises a ConfigurationInvalid error" do
+ expect { fetcher.validate! }.to raise_error Chef::Exceptions::Secret::ConfigurationInvalid
+ end
+ end
+
+ context "and region exists in AWS config and node attributes" do
+ let(:aws_global_config) { { region: "region-from-aws-global-config" } }
+ let(:node) { { "ec2" => { "region" => "region-from-ohai-data" } } }
+ it "uses the region from AWS config" do
+ fetcher.validate!
+ expect(fetcher.config[:region]).to eq "region-from-aws-global-config"
+ end
+ end
+
+ context "and region exists only in node attributes" do
+ let(:node) { { "ec2" => { "region" => "region-from-ohai-data" } } }
+ it "uses the region from AWS config" do
+ fetcher.validate!
+ expect(fetcher.config[:region]).to eq "region-from-ohai-data"
+ end
+
+ end
+
+ end
+end
diff --git a/spec/unit/secret_fetcher/azure_key_vault_spec.rb b/spec/unit/secret_fetcher/azure_key_vault_spec.rb
index cf8c5733c9..d41973992b 100644
--- a/spec/unit/secret_fetcher/azure_key_vault_spec.rb
+++ b/spec/unit/secret_fetcher/azure_key_vault_spec.rb
@@ -23,7 +23,7 @@ require "chef/secret_fetcher/azure_key_vault"
describe Chef::SecretFetcher::AzureKeyVault do
let(:config) { { vault: "myvault" } }
- let(:fetcher) { Chef::SecretFetcher::AzureKeyVault.new(config) }
+ 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
diff --git a/spec/unit/secret_fetcher_spec.rb b/spec/unit/secret_fetcher_spec.rb
index 545176f65a..f6fe2a90b5 100644
--- a/spec/unit/secret_fetcher_spec.rb
+++ b/spec/unit/secret_fetcher_spec.rb
@@ -28,7 +28,7 @@ class SecretFetcherImpl < Chef::SecretFetcher::Base
end
describe Chef::SecretFetcher do
- let(:fetcher_impl) { SecretFetcherImpl.new({}) }
+ let(:fetcher_impl) { SecretFetcherImpl.new({}, nil) }
before do
allow(Chef::SecretFetcher::Example).to receive(:new).and_return fetcher_impl
@@ -36,38 +36,38 @@ describe Chef::SecretFetcher do
context ".for_service" do
it "resolves the example fetcher without error" do
- Chef::SecretFetcher.for_service(:example, {})
+ Chef::SecretFetcher.for_service(:example, {}, nil)
end
it "resolves the Azure Key Vault fetcher without error" do
- Chef::SecretFetcher.for_service(:azure_key_vault, vault: "invalid")
+ Chef::SecretFetcher.for_service(:azure_key_vault, { vault: "invalid" }, nil)
end
it "resolves the AWS fetcher without error" do
- Chef::SecretFetcher.for_service(:aws_secrets_manager, region: "invalid")
+ Chef::SecretFetcher.for_service(:aws_secrets_manager, { region: "invalid" }, nil)
end
it "raises Chef::Exceptions::Secret::MissingFetcher when service is blank" do
- expect { Chef::SecretFetcher.for_service(nil, {}) }.to raise_error(Chef::Exceptions::Secret::MissingFetcher)
+ expect { Chef::SecretFetcher.for_service(nil, {}, nil) }.to raise_error(Chef::Exceptions::Secret::MissingFetcher)
end
it "raises Chef::Exceptions::Secret::MissingFetcher when service is nil" do
- expect { Chef::SecretFetcher.for_service("", {}) }.to raise_error(Chef::Exceptions::Secret::MissingFetcher)
+ expect { Chef::SecretFetcher.for_service("", {}, nil) }.to raise_error(Chef::Exceptions::Secret::MissingFetcher)
end
it "raises Chef::Exceptions::Secret::InvalidFetcher for an unknown fetcher" do
- expect { Chef::SecretFetcher.for_service(:bad_example, {}) }.to raise_error(Chef::Exceptions::Secret::InvalidFetcherService)
+ expect { Chef::SecretFetcher.for_service(:bad_example, {}, nil) }.to raise_error(Chef::Exceptions::Secret::InvalidFetcherService)
end
it "ensures fetcher configuration is valid by invoking validate!" do
expect(fetcher_impl).to receive(:validate!)
- Chef::SecretFetcher.for_service(:example, {})
+ Chef::SecretFetcher.for_service(:example, {}, nil)
end
end
context "#fetch" do
let(:fetcher) {
- Chef::SecretFetcher.for_service(:example, { "key1" => "value1" })
+ Chef::SecretFetcher.for_service(:example, { "key1" => "value1" }, nil)
}
it "fetches from the underlying service when secret name is provided " do