From 10724d72b5f2e4ac29350d2fb2e2498b8798b92a Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 15 Sep 2020 09:57:52 -0700 Subject: [PATCH] (PUP-8969) Preserve sensitiveness of epp templates If an EPP template contains a Sensitive data type, then join the unwrapped value, but wrap the entire result as Sensitive. This way the underlying sensitive value is preserved, and the caller can access it by unwrapping the entire result. Previously, the result was always a string and contained the generic 'Sensitive [redacted]' message, so the underlying sensitive value was lost. Update the `epp` and `inline_epp` function signatures, since they can both return either String or Sensitive[String]. Update the `epp` application to unwrap sensitive values produced by the above functions. --- lib/puppet/face/epp.rb | 14 +++++++++-- lib/puppet/functions/epp.rb | 1 + lib/puppet/functions/inline_epp.rb | 1 + lib/puppet/pops/evaluator/evaluator_impl.rb | 25 ++++++++++++++++--- spec/unit/functions/inline_epp_spec.rb | 27 ++++++++++++++++++++- 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/lib/puppet/face/epp.rb b/lib/puppet/face/epp.rb index 662c02039b2..a3d519cf612 100644 --- a/lib/puppet/face/epp.rb +++ b/lib/puppet/face/epp.rb @@ -440,7 +440,12 @@ def get_values(compiler, options) def render_inline(epp_source, compiler, options) template_args = get_values(compiler, options) - Puppet::Pops::Evaluator::EppEvaluator.inline_epp(compiler.topscope, epp_source, template_args) + result = Puppet::Pops::Evaluator::EppEvaluator.inline_epp(compiler.topscope, epp_source, template_args) + if result.instance_of?(Puppet::Pops::Types::PSensitiveType::Sensitive) + result.unwrap + else + result + end end def render_file(epp_template_name, compiler, options, show_filename, file_nbr) @@ -457,7 +462,12 @@ def render_file(epp_template_name, compiler, options, show_filename, file_nbr) if template_file.nil? && Puppet::FileSystem.exist?(epp_template_name) epp_template_name = File.expand_path(epp_template_name) end - output << Puppet::Pops::Evaluator::EppEvaluator.epp(compiler.topscope, epp_template_name, compiler.environment, template_args) + result = Puppet::Pops::Evaluator::EppEvaluator.epp(compiler.topscope, epp_template_name, compiler.environment, template_args) + if result.instance_of?(Puppet::Pops::Types::PSensitiveType::Sensitive) + output << result.unwrap + else + output << result + end rescue Puppet::ParseError => detail Puppet.err("--- #{epp_template_name}") if show_filename raise detail diff --git a/lib/puppet/functions/epp.rb b/lib/puppet/functions/epp.rb index f0d8399d2d7..cd563a6aa3c 100644 --- a/lib/puppet/functions/epp.rb +++ b/lib/puppet/functions/epp.rb @@ -40,6 +40,7 @@ scope_param param 'String', :path optional_param 'Hash[Pattern[/^\w+$/], Any]', :parameters + return_type 'Variant[String, Sensitive[String]]' end def epp(scope, path, parameters = nil) diff --git a/lib/puppet/functions/inline_epp.rb b/lib/puppet/functions/inline_epp.rb index affc3544134..7b191de6d34 100644 --- a/lib/puppet/functions/inline_epp.rb +++ b/lib/puppet/functions/inline_epp.rb @@ -51,6 +51,7 @@ scope_param() param 'String', :template optional_param 'Hash[Pattern[/^\w+$/], Any]', :parameters + return_type 'Variant[String, Sensitive[String]]' end def inline_epp(scope, template, parameters = nil) diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb index 446fc68247e..8bf13d22e86 100644 --- a/lib/puppet/pops/evaluator/evaluator_impl.rb +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -462,10 +462,24 @@ def calculate(left, right, bin_expr, scope) end def eval_EppExpression(o, scope) + contains_sensitive = false + scope["@epp"] = [] evaluate(o.body, scope) - result = scope["@epp"].join - result + result = scope["@epp"].map do |r| + if r.instance_of?(Puppet::Pops::Types::PSensitiveType::Sensitive) + contains_sensitive = true + string(r.unwrap, scope) + else + r + end + end.join + + if contains_sensitive + Puppet::Pops::Types::PSensitiveType::Sensitive.new(result) + else + result + end end def eval_RenderStringExpression(o, scope) @@ -474,7 +488,12 @@ def eval_RenderStringExpression(o, scope) end def eval_RenderExpression(o, scope) - scope["@epp"] << string(evaluate(o.expr, scope), scope) + result = evaluate(o.expr, scope) + if result.instance_of?(Puppet::Pops::Types::PSensitiveType::Sensitive) + scope["@epp"] << result + else + scope["@epp"] << string(result, scope) + end nil end diff --git a/spec/unit/functions/inline_epp_spec.rb b/spec/unit/functions/inline_epp_spec.rb index f50ae6e0d37..8b39eb5bc4f 100644 --- a/spec/unit/functions/inline_epp_spec.rb +++ b/spec/unit/functions/inline_epp_spec.rb @@ -1,8 +1,10 @@ - require 'spec_helper' +require 'puppet_spec/compiler' + describe "the inline_epp function" do include PuppetSpec::Files + include PuppetSpec::Compiler let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end @@ -73,6 +75,29 @@ expect(eval_template("string was: <%= $string %>")).to eq("string was: the string value") end + context "when using Sensitive" do + it "returns an unwrapped sensitive value as a String" do + expect(eval_and_collect_notices(<<~END)).to eq(["opensesame"]) + notice(inline_epp("<%= Sensitive('opensesame').unwrap %>")) + END + end + + it "rewraps a sensitive value" do + # note entire result is redacted, not just sensitive part + expect(eval_and_collect_notices(<<~END)).to eq(["Sensitive [value redacted]"]) + notice(inline_epp("This is sensitive <%= Sensitive('opensesame') %>")) + END + end + + it "can be double wrapped" do + catalog = compile_to_catalog(<<~END) + notify { 'title': + message => Sensitive(inline_epp("<%= Sensitive('opensesame') %>")) + } + END + expect(catalog.resource(:notify, 'title')['message']).to eq('opensesame') + end + end def eval_template_with_args(content, args_hash) epp_function.call(scope, content, args_hash)