diff options
author | Marc A. Paradise <marc.paradise@gmail.com> | 2021-07-07 17:42:05 -0400 |
---|---|---|
committer | Tim Smith <tsmith@chef.io> | 2021-07-14 08:07:35 -0700 |
commit | 60e6849aa65aece9ff896a765a92095337298d36 (patch) | |
tree | 6d61db57f3dc93512c2906eed327ff4c66a9c74b | |
parent | 9ebf86abc20aac7196e9628bd90b3256024cda10 (diff) | |
download | chef-60e6849aa65aece9ff896a765a92095337298d36.tar.gz |
Experimental support for an AWS Secrets Fetcher
In a recipe, usage will look like the following:
value = secret(name: "test1", service: :aws_secrets_manager, config: { region: "us-west-1" })
log "My secret is #{value.secret_string}"
Note the use of `secret_string` to determine the secret value. The
returned object here is Aws::Types::GetSecretValueResponse from the AWS SDK.
This beta implementation supports ec2/imds instance profile
authentication but also checks standard locations for credentials
configuration -- see documentation [1] for a description of default credentials search behavior.
[1] https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SecretsManager/Client.html#initialize-instance_method
Signed-off-by: Marc A. Paradise <marc.paradise@gmail.com>
-rw-r--r-- | Gemfile.lock | 15 | ||||
-rw-r--r-- | chef.gemspec | 1 | ||||
-rw-r--r-- | lib/chef/dsl/secret.rb | 9 | ||||
-rw-r--r-- | lib/chef/secret_fetcher.rb | 5 | ||||
-rw-r--r-- | lib/chef/secret_fetcher/aws_secrets_manager.rb | 61 | ||||
-rw-r--r-- | spec/unit/secret_fetcher_spec.rb | 6 |
6 files changed, 92 insertions, 5 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index be3c34aba1..373aa87087 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,6 +37,7 @@ PATH specs: chef (17.3.30) addressable + aws-sdk-secretsmanager (~> 1.46) chef-config (= 17.3.30) chef-utils (= 17.3.30) chef-vault @@ -64,6 +65,7 @@ PATH uuidtools (>= 2.1.5, < 3.0) chef (17.3.30-universal-mingw32) addressable + aws-sdk-secretsmanager (~> 1.46) chef-config (= 17.3.30) chef-utils (= 17.3.30) chef-vault @@ -133,6 +135,18 @@ GEM mixlib-cli (>= 1.4, < 3.0) mixlib-shellout (>= 2.0, < 4.0) ast (2.4.2) + aws-eventstream (1.1.1) + aws-partitions (1.474.0) + aws-sdk-core (3.116.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-secretsmanager (1.46.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.3) + aws-eventstream (~> 1, >= 1.0.2) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) builder (3.2.4) @@ -222,6 +236,7 @@ GEM inspec-core (= 4.38.3) ipaddress (0.8.3) iso8601 (0.13.0) + jmespath (1.4.0) json (2.5.1) libyajl2 (2.1.0) license-acceptance (2.1.13) diff --git a/chef.gemspec b/chef.gemspec index 76f562cc71..0f278fff8f 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -55,6 +55,7 @@ Gem::Specification.new do |s| s.add_dependency "proxifier", "~> 1.0" + s.add_dependency "aws-sdk-secretsmanager", "~> 1.46" s.bindir = "bin" s.executables = %w{ } diff --git a/lib/chef/dsl/secret.rb b/lib/chef/dsl/secret.rb index 5bb1cf5c34..9cc7d6b3da 100644 --- a/lib/chef/dsl/secret.rb +++ b/lib/chef/dsl/secret.rb @@ -33,16 +33,19 @@ class Chef # perform the secret lookup # @option config [Hash] The configuration that the named service expects # + # @return result [Object] The response object type is determined by the fetcher. See fetcher documentation + # to know what to expect for a given service. + # # @example # # This example uses the built-in :example secret manager service, which # accepts a hash of secrets. # - # value = secret(name: "test1", service: :example, config: { "test1" => "value1" } ) + # value = secret(name: "test1", service: :example, config: { "test1" => "value1" }) # log "My secret is #{value}" # - # value = secret(name: "test1", service: :aws_secrets_manager, config: { "region" => "us-west-1" }) - # log "My secret is #{value}" + # value = secret(name: "test1", service: :aws_secrets_manager, config: { region: "us-west-1" }) + # log "My secret is #{value.secret_string}" # # @note # diff --git a/lib/chef/secret_fetcher.rb b/lib/chef/secret_fetcher.rb index 31499da0fa..dcfb542aa6 100644 --- a/lib/chef/secret_fetcher.rb +++ b/lib/chef/secret_fetcher.rb @@ -21,7 +21,7 @@ require_relative "exceptions" class Chef class SecretFetcher - SECRET_FETCHERS = [ :example ].freeze + SECRET_FETCHERS = [ :example, :aws_secrets_manager ].freeze # Returns a configured and validated instance # of a [Chef::SecretFetcher::Base] for the given @@ -35,6 +35,9 @@ class Chef when :example require_relative "secret_fetcher/example" Chef::SecretFetcher::Example.new(config) + when :aws_secrets_manager + require_relative "secret_fetcher/aws_secrets_manager" + Chef::SecretFetcher::AWSSecretsManager.new(config) 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 new file mode 100644 index 0000000000..f890fb5f2e --- /dev/null +++ b/lib/chef/secret_fetcher/aws_secrets_manager.rb @@ -0,0 +1,61 @@ +# +# 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 "base" +require "aws-sdk-secretsmanager" + +class Chef + # == Chef::SecretFetcher::AWSSecretsManager + # A fetcher that fetches a secret from AWS Secrets Manager + # In this initial iteration it defaults to authentication via instance profile. + # It is possible to pass options that configure it to use alternative credentials. + # + # 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.fetch("secretkey1") + class SecretFetcher + class AWSSecretsManager < Base + DEFAULT_AWS_OPTS = { } + def validate! + # Note that we are not doing any validation of required configuration here, we will + # rely on the API client to do that for us, since it will work with the merge of + # the config we provide, env-based config, and/or an appropriate profile in ~/.aws + + # Instantiating the client is an opportunity for an API provider to do validation, + # so we'll do that first here. + client + end + + # @param identifier [String] the secret_id + # @return Aws::SecretsManager::Types::GetSecretValueResponse + def do_fetch(identifier) + client.get_secret_value(secret_id: identifier) + end + + def client + @client ||= Aws::SecretsManager::Client.new(DEFAULT_AWS_OPTS.merge(config)) + end + end + end +end diff --git a/spec/unit/secret_fetcher_spec.rb b/spec/unit/secret_fetcher_spec.rb index 3aa9efb5f1..c352585266 100644 --- a/spec/unit/secret_fetcher_spec.rb +++ b/spec/unit/secret_fetcher_spec.rb @@ -35,10 +35,14 @@ describe Chef::SecretFetcher do end context ".for_service" do - it "resolves a known secrets service to a fetcher" do + it "resolves the example fetcher without error" do Chef::SecretFetcher.for_service(:example, {}) end + it "resolves the AWS fetcher without error" do + Chef::SecretFetcher.for_service(:aws_secrets_manager, region: "invalid") + 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) end |