Skip to content

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 names pybritive checkout needs
  • britive::secret — a function, used with Deferred, 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

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 boto3

boto3 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-pipeline

Set the environment for the agent service

/etc/systemd/system/puppet.service.d/britive.conf
[Service]
Environment=BRITIVE_TENANT=your-tenant
Environment=BRITIVE_FEDERATION_PROVIDER=aws
Environment=BRITIVE_APPLICATION_NAME=AWS - Production
Environment=PYBRITIVE_BIN=/usr/local/bin/pybritive
systemctl daemon-reload && systemctl restart puppet

Source for the library and these adapters will be linked here once published.

Add the Fact and Function to a Module

Add the profiles fact

modules/britive_access/lib/facter/britive_profiles.rb
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
end

The account id comes from IMDSv2. Only profiles associated with this account are returned.

Add the secret function

modules/britive_access/lib/puppet/functions/britive/secret.rb
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
end

Returning Sensitive keeps the value out of logs and reports.

Use them in a manifest

manifests/app.pp
# 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_profiles

Expected 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.password

The file contains the password. The agent log shows content changed to [redacted].

Confirm nothing is stored

env | grep -c BRITIVE_API_TOKEN

Expected output: 0. On the Puppet server, the report for this node contains no secret value.

Troubleshoot

SymptomLikely CauseFix
Fact is {} with britive_profiles: pybritive not found in the logPYBRITIVE_BIN wrong for the agent’s environmentSet it in the systemd drop-in; pip often installs to ~/.local/bin
Fact is {} with a 403profiles.list is an administrative callGrant the service identity application-read, or skip the fact and use Deferred + checkout only
Deferred value is emptyPuppet < 6, or the function file not pluginsyncedUpgrade; check puppet agent -t --debug for britive/secret.rb
Password with a backslash renders wrong via HieraYAML consumes one backslash levelUse Deferred (no re-parse), or the library’s escape_fields: option to get a password_escaped sibling
cannot load such file -- britive/pipelineGem installed into system RubyReinstall with /opt/puppetlabs/puppet/bin/gem

Next Steps

Last updated on