From cb2f2d4bf9d326b5eb0792a5cb19a83f759454c0 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Wed, 15 Mar 2017 17:50:40 -0400 Subject: [PATCH 01/13] Version bump for dev --- lib/sfn-vault/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sfn-vault/version.rb b/lib/sfn-vault/version.rb index 5d93bfe..a96cc15 100644 --- a/lib/sfn-vault/version.rb +++ b/lib/sfn-vault/version.rb @@ -1,3 +1,3 @@ module SfnVault - VERSION = Gem::Version.new('0.1.2') + VERSION = Gem::Version.new('0.1.3') end From 5a7451aa62dc0aaee9b9445c857db62ffee6acfd Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Wed, 15 Mar 2017 16:45:16 -0400 Subject: [PATCH 02/13] Partially working DSL injection for a vault helper method --- lib/sfn-vault.rb | 3 ++- lib/sfn-vault/inject.rb | 32 ++++++++++++++++++++++++++++++++ sfn-vault.gemspec | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 lib/sfn-vault/inject.rb diff --git a/lib/sfn-vault.rb b/lib/sfn-vault.rb index e89b066..cba4c45 100644 --- a/lib/sfn-vault.rb +++ b/lib/sfn-vault.rb @@ -5,8 +5,9 @@ module SfnVault autoload :Platform, 'sfn-vault/platform' autoload :Windows, 'sfn-vault/windows' autoload :CertificateStore, 'sfn-vault/certificate_store' + autoload :Utils, 'sfn-vault/utils' end require 'sfn-vault/version' require 'sfn-vault/callback' - +require 'sfn-vault/inject' diff --git a/lib/sfn-vault/inject.rb b/lib/sfn-vault/inject.rb new file mode 100644 index 0000000..fab52a9 --- /dev/null +++ b/lib/sfn-vault/inject.rb @@ -0,0 +1,32 @@ +class SparkleFormation + module SparkleAttribute + module Aws + + require 'vault' + require 'securerandom' + + # Polluting SparkleFormation::SparkleAttribute::Aws namespace ? + include SfnVault::Utils + + # example usage: vault_parameter!(:masterpassword) + def _vault_parameter(*vp_args) + vp_name, vp_opts = vp_args + __t_stringish(vp_name) + if vp_opts + vp_path = vp_opts[:path] if vp_opts[:path] + end + parameters.set!("vault_parameter_#{vp_name}") do + no_echo true + description "Automatically generated Vault param #{vp_name}" + type 'String' + end + end + alias_method :vault_parameter!, :_vault_parameter + end + end +end + +# do stuff +# save to vault cubbyhole/name, ex: cubbyhole/masterpassword +# generate parameter json with NoEcho true +# inject parameter value, see sfn-parameters diff --git a/sfn-vault.gemspec b/sfn-vault.gemspec index 08c23c1..9114b8c 100644 --- a/sfn-vault.gemspec +++ b/sfn-vault.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.license = 'Apache-2.0' s.require_path = 'lib' s.add_dependency 'sfn', '>= 3.0', '< 4.0' - s.add_dependency 'vault', '~> 0.7.3' + s.add_dependency 'vault', '~> 0.9.0' s.add_dependency 'ffi', '~> 1.9.14' s.add_development_dependency 'pry', '~> 0.10.4' s.add_development_dependency 'pry-byebug', '~> 3.4', '>= 3.4.2' From 7ea46891f60fce47937dd8e1c1df911428f276e5 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Tue, 28 Mar 2017 18:27:38 -0400 Subject: [PATCH 03/13] Simplify DSL helper method: vault_parameter! This simplifies the vault helper method provided to the template DSL to simply insert a named parameter with a generic description and NoEcho set. This parameter uses a custom type that is not recognized by AWS and will cause a validation error if not replaced. The template callback will handle finding all parameters of this custom type, generating values for them and saving them safely in Vault, either to an appropriate project path or to the cubbyhole for the current token. The type is then converted to 'String' and the value is filled from Vault during stack creation. --- lib/sfn-vault/inject.rb | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/lib/sfn-vault/inject.rb b/lib/sfn-vault/inject.rb index fab52a9..e94512c 100644 --- a/lib/sfn-vault/inject.rb +++ b/lib/sfn-vault/inject.rb @@ -2,31 +2,17 @@ class SparkleFormation module SparkleAttribute module Aws - require 'vault' - require 'securerandom' - - # Polluting SparkleFormation::SparkleAttribute::Aws namespace ? - include SfnVault::Utils - - # example usage: vault_parameter!(:masterpassword) - def _vault_parameter(*vp_args) - vp_name, vp_opts = vp_args + # A small helper method for adding the specific named + # parameter struct with the custom type + def _vault_parameter(vp_name) __t_stringish(vp_name) - if vp_opts - vp_path = vp_opts[:path] if vp_opts[:path] - end - parameters.set!("vault_parameter_#{vp_name}") do + parameters.set!(vp_name) do no_echo true - description "Automatically generated Vault param #{vp_name}" - type 'String' + description "Generated secret automatically stored in Vault" + type 'Vault::Generic::Secret' end end alias_method :vault_parameter!, :_vault_parameter end end end - -# do stuff -# save to vault cubbyhole/name, ex: cubbyhole/masterpassword -# generate parameter json with NoEcho true -# inject parameter value, see sfn-parameters From 49e79e08ea2e20733a137eefadb244b3734418e8 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 17:35:08 -0400 Subject: [PATCH 04/13] Add callback to process psuedo parameters with Vault values Add callback to lookup all psuedo parameters in the template and get/set a randomized value from Vault --- lib/sfn-vault/callback.rb | 90 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index c36f7dd..2e2e33e 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -1,10 +1,13 @@ require 'sfn-parameters' +require 'securerandom' +require 'vault' # Modeled after the Assume Role callback module Sfn class Callback class VaultRead < Callback + include SfnVault::Utils # Cache credentials for re-use to prevent re-generation of temporary @@ -16,10 +19,91 @@ class VaultRead < Callback :aws_secret_access_key ] - # Inject credentials read from vault path - # into API provider configuration + def template(*args) + # search for all parameters of type 'Vault::Generic::Secret' + # 1. use the sparkleformation instance to get at the parameter hash, + config[:parameters] ||= Smash.new + stack = args.first[:sparkle_stack] + # 2. find names for things you want, + pseudo_parameters(stack).each do |param| + param_path = vault_path_name(args, param) + ui.debug "Using #{param_path} for saved parameter" + # check if already saved in vault + # Save the secret unless one already exists at the defined path + client = vault_client + unless client.logical.read(param_path) + ui.info "Vault: No pre-existing value for parameter #{param} saving new secret" + client.logical.write(param_path, value: random_secret) + end + # Read saved secret back from Vault and update parameters config + # 3. set param into config + config[:parameters][param] = client.logical.read(param_path).data[:value] + # 4. update type in template and that should do it + stack.compile.parameters.set!(param).type 'String' + end + end + + # Use SecureRandom to generate a suitable password + # Length is configurable by setting `pseudo_parameter_length` in the vault + # section of the sfn config + # + # @return [String] The generated string + def random_secret + SecureRandom.base64(config.fetch(:vault, :pseudo_parameter_length, 15)) + end + + # Build the path where generated secrets can be saved in Vault + # This will use the value of `:parameter_path` from the config if set. If + # unset it will attempt to build a type of standardized path based on the + # combined value any stack 'Project' tag and Stack name. + # Project will fallback to 'SparkleFormation' if unset + # + # @param args [Array] Array of args passed to the sfn instance + # @param parameter [String] Template parameter to save value for in vault + # @return [String] String value or stack name if available or default to template name + def vault_path_name(args, parameter) + return config.fetch(:vault, :pseudo_parameter_path, nil) if config.fetch(:vault, :pseudo_parameter_path, nil) + # If we have a stack name use it, otherwise try to get from env and fallback to just template name + stack_name = args.first[:stack_name].nil? ? ENV.fetch('STACK_NAME', stack.name).to_s : args.first[:stack_name] + project = config[:options][:tags].fetch('Project', 'SparkleFormation') + + # When running in a detectable CI environment assume that we have rights to save a generic secret + vault_path = if ci_environment? + # write to vault at generic path + "/secret/#{project}/#{stack_name}/#{parameter}" + else + # or for local dev use /cubbyhole/Parameter + "/cubbyhole/#{parameter}" + end + vault_path + end + + # Lookup all pseudo parameters in the template + # + # @param stack [SparkleFormation] An instance of the stack template + # @param parameter [String] The string value of the pseudo type to lookup + # @return [Array] Array of parameter names matching the pseudo type + def pseudo_parameters(stack, parameter: 'Vault::Generic::Secret') + stack.dump.fetch('Parameters', {}).map{|k,v| k if v['Type'] == parameter}.compact + end + + + # Check if we are running in any detectable CI type environments + # + # @return [TrueClass, FalseClass] + def ci_environment? + # check for any ci system env variables + return true if ENV['GO_PIPELINE_NAME'] + return true if ENV['CI'] + false + end + def after_config(*_) + # Inject credentials read from configured vault path + # into API provider configuration # if credentials block contains vault_read_path + # TODO: this could be done earlier if at all possible so credentials + # struct does not need the aws config if(enabled? && config.fetch(:credentials, :vault_read_path)) load_stored_session end @@ -48,7 +132,7 @@ def enabled? config.fetch(:vault, :status, 'enabled').to_s == 'enabled' end - # @return String path + # @return [String ]path def cache_file config.fetch(:vault, :cache_file, '.sfn-vault') end From 48b99d4c8e508fb393ae6f4afff4a1a663b02f73 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 17:48:55 -0400 Subject: [PATCH 05/13] Switch to .get --- lib/sfn-vault/callback.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index 2e2e33e..0bbe01f 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -62,7 +62,7 @@ def random_secret # @param parameter [String] Template parameter to save value for in vault # @return [String] String value or stack name if available or default to template name def vault_path_name(args, parameter) - return config.fetch(:vault, :pseudo_parameter_path, nil) if config.fetch(:vault, :pseudo_parameter_path, nil) + return config.get(:vault, :pseudo_parameter_path) if config.get(:vault, :pseudo_parameter_path) # If we have a stack name use it, otherwise try to get from env and fallback to just template name stack_name = args.first[:stack_name].nil? ? ENV.fetch('STACK_NAME', stack.name).to_s : args.first[:stack_name] project = config[:options][:tags].fetch('Project', 'SparkleFormation') From 62288b2294393a24b3af0dab0c91680e515aed90 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 17:50:41 -0400 Subject: [PATCH 06/13] Fix doc string for vault_path_name --- lib/sfn-vault/callback.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index 0bbe01f..4a4dd90 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -53,7 +53,7 @@ def random_secret end # Build the path where generated secrets can be saved in Vault - # This will use the value of `:parameter_path` from the config if set. If + # This will use the value of `:pseudo_parameter_path` from the config if set. If # unset it will attempt to build a type of standardized path based on the # combined value any stack 'Project' tag and Stack name. # Project will fallback to 'SparkleFormation' if unset From 7fc20caae567c3dd39ff5a3dcb1ae49e273c72ff Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 18:03:53 -0400 Subject: [PATCH 07/13] Missing stack object in vault_path_name --- lib/sfn-vault/callback.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index 4a4dd90..3c6f606 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -64,6 +64,7 @@ def random_secret def vault_path_name(args, parameter) return config.get(:vault, :pseudo_parameter_path) if config.get(:vault, :pseudo_parameter_path) # If we have a stack name use it, otherwise try to get from env and fallback to just template name + stack = args.first[:sparkle_stack] stack_name = args.first[:stack_name].nil? ? ENV.fetch('STACK_NAME', stack.name).to_s : args.first[:stack_name] project = config[:options][:tags].fetch('Project', 'SparkleFormation') From 408ad30eca10d15fb56f5e3d74d0da426f512fdb Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 18:16:15 -0400 Subject: [PATCH 08/13] Switch to user configurable base instead of full path --- lib/sfn-vault/callback.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index 3c6f606..3c768e1 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -62,7 +62,8 @@ def random_secret # @param parameter [String] Template parameter to save value for in vault # @return [String] String value or stack name if available or default to template name def vault_path_name(args, parameter) - return config.get(:vault, :pseudo_parameter_path) if config.get(:vault, :pseudo_parameter_path) + pref = config.get(:vault, :pseudo_parameter_path) + base = pref.nil? ? "secret" : pref # If we have a stack name use it, otherwise try to get from env and fallback to just template name stack = args.first[:sparkle_stack] stack_name = args.first[:stack_name].nil? ? ENV.fetch('STACK_NAME', stack.name).to_s : args.first[:stack_name] @@ -71,7 +72,7 @@ def vault_path_name(args, parameter) # When running in a detectable CI environment assume that we have rights to save a generic secret vault_path = if ci_environment? # write to vault at generic path - "/secret/#{project}/#{stack_name}/#{parameter}" + "/#{base}/#{project}/#{stack_name}/#{parameter}" else # or for local dev use /cubbyhole/Parameter "/cubbyhole/#{parameter}" From 4d9a4a2bbc66ea7b689896b766c6b2239363b86f Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 18:19:28 -0400 Subject: [PATCH 09/13] Add documentation on pseudo parameter behavior --- README.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2f88b2..1e69f71 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,55 @@ group :sfn do end ~~~ +### Vault Pseudo Parameters + +This callback provides a method to set and store template parameters in a +configured Vault instance. This is implemented by adding special handling for a +custom parameter 'type'. Cloudformation parameters can have +option +[AWS-Specific Parameter Types](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html?shortFooter=true#aws-specific-parameter-types). +This callback looks for a special type named `Vault::Generic::Secret` and will +dynamically get or set a value from vault for parameters with this type, then +set the actual type to 'String' which can be understood by AWS. + +Generally these should be set as `NoEcho` parameters and a dsl helper method is +provided to generate this type of named parameter. + +Example usage in template: +~~~ruby +vault_parameter!(:secret_value) +~~~ + +Will result in a template with the following parameter defined: +~~~json +"SecretValue": { + "NoEcho": true, + "Description": "Generated secret automatically stored in Vault", + "Type": "String" +} +~~~ + +The value will be stored in vault and retrieved dynamically at stack creation +time. If the `sfn` command is running in a CI environment, where the `CI` +environment variable is set, then the callback will attempt to use the default +generic secret backed path in a stack specific location. + +For local development needs or if this environment variable is undetected the +vault cubbyhole is used. + +The path is configurable by using the `:pseudo_parameter_path` in the sfn config: + +~~~ruby +... +vault do + pseudo_parameter_path "/secret/aws_secrets" +end +~~~ + +By default 15 character base64 strings are generated using SecureRandom. The +length can be adjusted by setting `:pseudo_parameter_length` in the config to +any integer value. + ### Vault Read #### Enable @@ -43,7 +92,7 @@ end #### Configuration The default read path is `aws/creds/deploy` and will be used without -configuration but it is customizable. +configuration but it is customizable. Vault read configuration is controlled within the `.sfn` file: From c38b0193dd874e9c7071f0dfbbada471a6e60051 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 23:30:04 -0400 Subject: [PATCH 10/13] Update and reorganize readme --- README.md | 109 +++++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 1e69f71..b09d7ed 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# SparkleFormation Vault Read Callback +# SparkleFormation Vault Callback Provides a mechanism to read dynamic credentials for use with AWS Orchestration APIs from a [Vault](https://www.vaultproject.io/intro/getting-started/dynamic-secrets.html) secret backend. +Also provides a method to set and store template parameters in a configured +Vault instance. This is implemented by handling a custom parameter 'type'. + **This is early alpha quality code** Currently supported cloud providers: @@ -24,55 +27,6 @@ group :sfn do end ~~~ -### Vault Pseudo Parameters - -This callback provides a method to set and store template parameters in a -configured Vault instance. This is implemented by adding special handling for a -custom parameter 'type'. Cloudformation parameters can have -option -[AWS-Specific Parameter Types](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html?shortFooter=true#aws-specific-parameter-types). -This callback looks for a special type named `Vault::Generic::Secret` and will -dynamically get or set a value from vault for parameters with this type, then -set the actual type to 'String' which can be understood by AWS. - -Generally these should be set as `NoEcho` parameters and a dsl helper method is -provided to generate this type of named parameter. - -Example usage in template: -~~~ruby -vault_parameter!(:secret_value) -~~~ - -Will result in a template with the following parameter defined: -~~~json -"SecretValue": { - "NoEcho": true, - "Description": "Generated secret automatically stored in Vault", - "Type": "String" -} -~~~ - -The value will be stored in vault and retrieved dynamically at stack creation -time. If the `sfn` command is running in a CI environment, where the `CI` -environment variable is set, then the callback will attempt to use the default -generic secret backed path in a stack specific location. - -For local development needs or if this environment variable is undetected the -vault cubbyhole is used. - -The path is configurable by using the `:pseudo_parameter_path` in the sfn config: - -~~~ruby -... -vault do - pseudo_parameter_path "/secret/aws_secrets" -end -~~~ - -By default 15 character base64 strings are generated using SecureRandom. The -length can be adjusted by setting `:pseudo_parameter_length` in the config to -any integer value. - ### Vault Read #### Enable @@ -138,6 +92,61 @@ Configuration.new end ~~~ +### Vault Pseudo Parameters +Cloudformation parameters can have +optional +[AWS-Specific Parameter Types](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html?shortFooter=true#aws-specific-parameter-types). +In a similar way this callback looks for a special parameter type named +`Vault::Generic::Secret` and will dynamically get or set a key value from Vault. +The key will be named to match the parameters name. Then the parameter type in the +template is changed to 'String' which can be understood by AWS. + +Generally these should be set as `NoEcho` parameters and a dsl helper method is +provided to generate this type of named parameter. + +Example usage in template: +~~~ruby +vault_parameter!(:secret_value) +~~~ + +Will result in a template with the following parameter defined: +~~~json +"Parameters": { + "SecretValue": { + "NoEcho": true, + "Description": "Generated secret automatically stored in Vault", + "Type": "String" + } +} +~~~ + +And the value of this parameter will be stored and retrieved from a Vault key by default named: + +~~~ +cubbyhole/ +~~~ + +The value will be stored in vault and retrieved dynamically at stack creation +time. If the `sfn` command is running in a CI environment, where the `CI` +environment variable is set, then the callback will attempt to use the default +generic secret backed path in a stack specific location. + +For local development needs or if this environment variable is undetected the +vault cubbyhole is used. + +The path is configurable by using the `:pseudo_parameter_path` in the sfn config: + +~~~ruby +... +vault do + pseudo_parameter_path "/secret/aws_secrets" +end +~~~ + +By default 15 character base64 strings are generated using SecureRandom. The +length can be adjusted by setting `:pseudo_parameter_length` in the config to +any integer value. + # Info * Repository: https://github.com/webframp/sfn-vault From b3e34ac0ce5435b8e19de25fef6621cb2c2f40f3 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 23:31:07 -0400 Subject: [PATCH 11/13] Use File.join instead of interpolation for vault_path value This makes it easier to honor the user preference for the base path component and will handl empty values for any of the path components in a sane way. --- lib/sfn-vault/callback.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index 3c768e1..89099aa 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -63,20 +63,23 @@ def random_secret # @return [String] String value or stack name if available or default to template name def vault_path_name(args, parameter) pref = config.get(:vault, :pseudo_parameter_path) - base = pref.nil? ? "secret" : pref # If we have a stack name use it, otherwise try to get from env and fallback to just template name stack = args.first[:sparkle_stack] stack_name = args.first[:stack_name].nil? ? ENV.fetch('STACK_NAME', stack.name).to_s : args.first[:stack_name] project = config[:options][:tags].fetch('Project', 'SparkleFormation') # When running in a detectable CI environment assume that we have rights to save a generic secret + # but honor user preference value if set vault_path = if ci_environment? - # write to vault at generic path - "/#{base}/#{project}/#{stack_name}/#{parameter}" + # write to vault at generic path + base = pref.nil? ? "secret" : pref + File.join(base, project, stack_name, parameter) else - # or for local dev use /cubbyhole/Parameter - "/cubbyhole/#{parameter}" + base = pref.nil? ? "cubbyhole" : pref + # or for local dev use cubbyhole + File.join(base, project, stack_name, parameter) end + ui.debug "Vault: generated parameter value will be stored at #{vault_path}" vault_path end From 9f5ccf835e1b7d406082f7ec22478c045f949546 Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 23:54:32 -0400 Subject: [PATCH 12/13] Move vault client outside of parameter handling loop Re-use the established client instead of creating a new client for each parameter request. Remove extra debug info --- lib/sfn-vault/callback.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/sfn-vault/callback.rb b/lib/sfn-vault/callback.rb index 89099aa..911c1ca 100644 --- a/lib/sfn-vault/callback.rb +++ b/lib/sfn-vault/callback.rb @@ -25,12 +25,12 @@ def template(*args) config[:parameters] ||= Smash.new stack = args.first[:sparkle_stack] # 2. find names for things you want, + client = vault_client pseudo_parameters(stack).each do |param| param_path = vault_path_name(args, param) ui.debug "Using #{param_path} for saved parameter" # check if already saved in vault # Save the secret unless one already exists at the defined path - client = vault_client unless client.logical.read(param_path) ui.info "Vault: No pre-existing value for parameter #{param} saving new secret" client.logical.write(param_path, value: random_secret) @@ -79,7 +79,6 @@ def vault_path_name(args, parameter) # or for local dev use cubbyhole File.join(base, project, stack_name, parameter) end - ui.debug "Vault: generated parameter value will be stored at #{vault_path}" vault_path end From b2954a3f45e2ced660338d2d06904b8437704e4d Mon Sep 17 00:00:00 2001 From: Sean Escriva Date: Thu, 30 Mar 2017 23:55:51 -0400 Subject: [PATCH 13/13] More documentation --- README.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b09d7ed..d0ef3a8 100644 --- a/README.md +++ b/README.md @@ -120,21 +120,28 @@ Will result in a template with the following parameter defined: } ~~~ -And the value of this parameter will be stored and retrieved from a Vault key by default named: +And the value of this parameter will be stored and retrieved from a stack +specific Vault key by default named like: ~~~ -cubbyhole/ +cubbyhole/SparkleFormation/template/SecretValue ~~~ +In this example the template is named 'template', but this will be replaced with +the stack name during create/update operations. + The value will be stored in vault and retrieved dynamically at stack creation time. If the `sfn` command is running in a CI environment, where the `CI` environment variable is set, then the callback will attempt to use the default -generic secret backed path in a stack specific location. +generic secret backed path of `secret` in a stack specific location. For local development needs or if this environment variable is undetected the vault cubbyhole is used. -The path is configurable by using the `:pseudo_parameter_path` in the sfn config: +The base path is also configurable by using the `:pseudo_parameter_path` in the +sfn config. This replaces either `cubbyhole` or `secret` in the vault key used +to store the parameter values. If configured this base path will override any CI +environment detection and always be honored. ~~~ruby ... @@ -147,6 +154,20 @@ By default 15 character base64 strings are generated using SecureRandom. The length can be adjusted by setting `:pseudo_parameter_length` in the config to any integer value. +# Known Issues + +If the iam_delay for the vault read behavior is not long enough the generated +credentials will not be available for use and api requests will fail. As noted +in +the +[Vault documentation](https://www.vaultproject.io/docs/secrets/aws/index.html#dynamic-iam-users) this +is a limitation due to the eventually consistent behavior of IAM credentials. If +you need the credentials to be immediately available, it is suggested to use +the +[STS Callback](http://www.sparkleformation.io/docs/sfn/callbacks.html#aws-assume-role) and +set thestatus to `disabled` in the vault config section. This will disable the +vault read behavior but still handles pseudo parameters. + # Info * Repository: https://github.com/webframp/sfn-vault