Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,47 @@ infrastructure is assumed to be up and reachable by your Puppet agents.
Install this module as you would in any other; the necessary code will
be distributed to Puppet agents via pluginsync.

In your manifests, call the `vault_lookup::lookup()` function using the
Deferred type. For example:
Configure Vault server in Hiera:

```yaml
vault_lookup::server: https://vault.hostname:8200
```
get secret from Vault:

```puppet
$d = vault_lookup::kv('secret/test')
```
which will return `Deferred` function (evaluated on agent side).

When working with `$d` (Deferred object), you can use `vault_lookup::fmt()` function (where you can apply `sprintf` formatting):
```puppet
file { '/path/to/your/file':
content => vault_lookup::fmt('password=%<pass>s', {'pass' => $d.unwrap }),
}
```
Or you could use directly `Deferred` object:

```puppet
file { '/etc/secrets.conf':
ensure => file,
content => Deferred('inline_epp',
['PASSWORD=<%= $password.unwrap %>', {'password' => $d}]),
}
```

Optionally you can specify which `field` should be retrieved:

```puppet
$d = vault_lookup::kv('secret/test', {'field' => 'password')
```

If needed, you can override Vault server address:

```puppet
$d = vault_lookup::kv('secret/test', {'vault_addr' => 'https://vault.hostname:8200')
```

Internally `vault_lookup::kv` creates a `Deferred` object:

```puppet
$d = Deferred('vault_lookup::lookup', ["secret/test", 'https://vault.hostname:8200'])
Expand All @@ -72,7 +111,7 @@ You can also choose not to specify the Vault URL, and then Puppet will use the
set in the service config file for Puppet, on Debian `/etc/default/puppet`, on RedHat
`/etc/sysconfig/puppet`:

```
```puppet
$d = Deferred('vault_lookup::lookup', ["secret/test"])

node default {
Expand Down
21 changes: 21 additions & 0 deletions lib/puppet/functions/vault_lookup/fmt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

Puppet::Functions.create_function('vault_lookup::fmt') do
# @summary Lazy format function
# Will be evaluated on client side - not during catalog compile on server
# @param format Ruby printf syntax
# @param args arguments passed to sprintf function
# @see https://idiosyncratic-ruby.com/49-what-the-format.html
# @example
# vault::fmt("%<x>d + %<y>d = %<z>d", {'x' => 2, 'y' => 2, 'z' => 5})
# won't work on Puppet 7
dispatch :fmt do
param 'String', :format
param 'Hash', :args
return_type 'Deferred'
end

def fmt(format, args)
Puppet::Pops::Types::TypeFactory.deferred.create('sprintf', [format, call_function('vault_lookup::unpack', args)])
end
end
28 changes: 28 additions & 0 deletions lib/puppet/functions/vault_lookup/kv.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

Puppet::Functions.create_function('vault_lookup::kv') do
# @summary Fetches secret stored under $key from Vault server using deferred function
# @param key Secret key path in Vault
# @param ops Options passed to Vault
#
# kv2 is prefixed with `"kv/data`
#
# @example
# vault::kv('secret')
# vault::kv('secret', {'field' => 'password'})
dispatch :kv do
param 'String', :key
optional_param 'Hash', :opts
return_type 'Deferred'
end

def kv(key, opts = {})
unless opts.key?('vault_addr')
type_parser = Puppet::Pops::Types::TypeParser.singleton
vault_addr = call_function('lookup', 'vault_lookup::server', type_parser.parse('Optional[String]'), nil, nil)
opts['vault_addr'] = vault_addr if vault_addr
end

Puppet::Pops::Types::TypeFactory.deferred.create('vault_lookup::lookup', [key, opts])
end
end
20 changes: 20 additions & 0 deletions lib/puppet/functions/vault_lookup/unpack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

Puppet::Functions.create_function('vault_lookup::unpack') do
# @summary Unwrap values in a Hash
# @param args Hash possibly with Sensitive data
dispatch :unpack do
param 'Hash', :args
return_type 'Hash'
end

def unpack(args)
args.transform_values do |value|
if value.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
call_function('unwrap', value)
else
value
end
end
end
end
19 changes: 19 additions & 0 deletions spec/functions/fmt_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'vault_lookup::fmt' do
context 'deferred array format' do
it {
exp = Puppet::Pops::Types::TypeFactory.deferred.create('sprintf', ['foo=%<bar>d', { 'bar' => 2 }])
# This kind_of matcher requires https://github.com/puppetlabs/rspec-puppet/pull/24
expect(subject).to run.with_params(
'foo=%<bar>d', { 'bar' => 2 }
).and_return(kind_of(Puppet::Pops::Types::PuppetObject))

expect(subject).to run.with_params(
'foo=%<bar>d', { 'bar' => 2 }
).and_return(exp)
}
end
end
51 changes: 51 additions & 0 deletions spec/functions/kv_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'vault_lookup::kv' do
context 'with server address' do
it {
exp = Puppet::Pops::Types::TypeFactory.deferred.create('vault_lookup::lookup', ['kv/data/secret', { 'vault_addr' => 'https://vault.example.com:8200' }])
# This kind_of matcher requires https://github.com/puppetlabs/rspec-puppet/pull/24
expect(subject).to run.with_params(
'kv/data/secret'
).and_return(kind_of(Puppet::Pops::Types::PuppetObject))

expect(subject).to run.with_params(
'kv/data/secret', { 'vault_addr' => 'https://vault.example.com:8200' }
).and_return(exp)
}
end

context 'with lookup key' do
let(:hiera_data) { { 'vault_lookup::server' => 'https://vault:8200' } }

it {
exp = Puppet::Pops::Types::TypeFactory.deferred.create('vault_lookup::lookup', ['secret', {}])

expect(subject).to run.with_params(
'secret'
).and_return(exp)
}
end

context 'with vault_addr key' do
it {
exp = Puppet::Pops::Types::TypeFactory.deferred.create('vault_lookup::lookup', ['secret', { 'vault_addr' => 'https://vault:8200' }])

expect(subject).to run.with_params(
'secret', { 'vault_addr' => 'https://vault:8200' }
).and_return(exp)
}
end

context 'with field key' do
it {
exp = Puppet::Pops::Types::TypeFactory.deferred.create('vault_lookup::lookup', ['secret', { 'field' => 'password' }])

expect(subject).to run.with_params(
'secret', { 'field' => 'password' }
).and_return(exp)
}
end
end
21 changes: 21 additions & 0 deletions spec/functions/unpack_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'vault_lookup::unpack' do
let(:function) { subject }

it 'converts sensitive Hash' do
exp = { 'password' => 'p1ssw0rd' }
expect(subject).to run.with_params(
{ 'password' => sensitive('p1ssw0rd') }
).and_return(exp)
end

it 'does nothing with String type' do
h = { 'foo' => 'bar', 'boo' => 'baz' }
expect(subject).to run.with_params(
{ 'foo' => 'bar', 'boo' => 'baz' }
).and_return(h)
end
end
Loading