Puppet
Overview
A Puppet-managed node often needs a database password, an API key, or short-lived cloud credentials during a run. This guide gives Puppet two things from Britive, with no Britive token on the node:
britive_profiles— a fact listing the access profiles available to this node’s AWS account, with the fully qualified namespybritive checkoutneedsbritive::secret— a function, used withDeferred, that reads a vault secret on the agent at apply time
Both sit on a small Ruby library that drives the PyBritive CLI with the AWS workload federation flag.
What you’ll accomplish:
- Install PyBritive and the library into the Puppet agent’s Ruby
- Add the fact and the function to a module
- Write a manifest that places a secret in a file without it ever leaving the node
Prerequisites
- Puppet 6 or later on the agent (
Deferredrequires Puppet ≥ 6.0). Puppet 7 and 8 tested. - Nodes running on AWS with an instance role
- Britive configured to trust that role — complete Authenticate from AWS with Workload Federation first
- Your tenant name — see Finding Your Tenant Name
How It Works
flowchart LR
subgraph node["Puppet agent node"]
facter["Facter<br/>britive_profiles"]
deferred["Deferred<br/>britive::secret"]
gem["britive-pipeline<br/>Ruby library"]
py["pybritive -P aws"]
facter --> gem
deferred --> gem
gem -->|argv, no shell| py
end
server["Puppet server<br/>PuppetDB"]
britive["Britive"]
py -->|instance role → service identity| britive
facter -.->|facts upload| server
The library runs pybritive with --silent --format json and the federation provider flag, parses the JSON, and raises typed errors. It never builds a shell string and never handles a token.
Facts are uploaded to the Puppet server and stored in PuppetDB and reports. Put profile names in a fact; put secret values behind Deferred, so they are resolved on the agent and never enter the catalog or PuppetDB.
Install on the Agent
Install PyBritive
pip install pybritive boto3boto3 is required by the aws federation provider.
Install the library into Puppet’s Ruby
Puppet ships its own Ruby. Install there, not into the system Ruby:
/opt/puppetlabs/puppet/bin/gem install britive-pipelineSet the environment for the agent service
[Service]
Environment=BRITIVE_TENANT=your-tenant
Environment=BRITIVE_FEDERATION_PROVIDER=aws
Environment=BRITIVE_APPLICATION_NAME=AWS - Production
Environment=PYBRITIVE_BIN=/usr/local/bin/pybritivesystemctl daemon-reload && systemctl restart puppetSource for the library and these adapters will be linked here once published.
Add the Fact and Function to a Module
Add the profiles fact
require 'britive/pipeline'
Facter.add(:britive_profiles) do
setcode do
begin
cli = Britive::Pipeline::CLI.new
profiles = Britive::Pipeline::Profiles.for_application(name: ENV.fetch('BRITIVE_APPLICATION_NAME'), cli: cli)
profiles.names(environment: Britive::Pipeline::AwsMetadata.account_id)
rescue Britive::Pipeline::Error => e
Facter.warn("britive_profiles: #{e.message}") # warn, never raise: a raise fails every catalog
{}
end
end
endThe account id comes from IMDSv2. Only profiles associated with this account are returned.
Add the secret function
Puppet::Functions.create_function(:'britive::secret') do
dispatch :secret do
param 'String', :path
optional_param 'String', :field
return_type 'Sensitive'
end
def secret(path, field = nil)
require 'britive/pipeline'
value = Britive::Pipeline.secrets.view(path)
value = value.fetch(field) if field && value.is_a?(Hash)
Puppet::Pops::Types::PSensitiveType::Sensitive.new(value)
end
endReturning Sensitive keeps the value out of logs and reports.
Use them in a manifest
# Secret resolved on the agent at apply time; never in the catalog.
file { '/etc/app/db.password':
ensure => file,
mode => '0600',
content => Deferred('britive::secret', ['/Team Secrets/database', 'password']),
}
# Profile names are not secret; a fact is fine.
notify { "Britive profiles here: ${join(keys($facts['britive_profiles']), ', ')}": }Verify
Check the fact
/opt/puppetlabs/bin/facter -p britive_profilesExpected output:
{
ReadOnly => {
fully_qualified_name => "AWS - Production/123456789012 (prod)/ReadOnly",
profile_id => "…"
}
}Apply the manifest
/opt/puppetlabs/bin/puppet agent -t
cat /etc/app/db.passwordThe file contains the password. The agent log shows content changed to [redacted].
Confirm nothing is stored
env | grep -c BRITIVE_API_TOKENExpected output: 0. On the Puppet server, the report for this node contains no secret value.
Troubleshoot
| Symptom | Likely Cause | Fix |
|---|---|---|
Fact is {} with britive_profiles: pybritive not found in the log | PYBRITIVE_BIN wrong for the agent’s environment | Set it in the systemd drop-in; pip often installs to ~/.local/bin |
Fact is {} with a 403 | profiles.list is an administrative call | Grant the service identity application-read, or skip the fact and use Deferred + checkout only |
Deferred value is empty | Puppet < 6, or the function file not pluginsynced | Upgrade; check puppet agent -t --debug for britive/secret.rb |
| Password with a backslash renders wrong via Hiera | YAML consumes one backslash level | Use Deferred (no re-parse), or the library’s escape_fields: option to get a password_escaped sibling |
cannot load such file -- britive/pipeline | Gem installed into system Ruby | Reinstall with /opt/puppetlabs/puppet/bin/gem |
Next Steps
- Authenticate from AWS with Workload Federation — the identity model this depends on
- Scripting PyBritive — the flags and patterns the library encodes
- Zero-Secret Workloads — why no token is the goal