# frozen_string_literal: true require 'facter' require 'yaml' require 'net/http' require 'openssl' require 'uri' require 'fileutils' # EncapiENC module: Fetches ENC data from encapi, caches it, and provides structured facts. module EncapiENC CACHE_FILE = '/var/cache/puppet_enc.yaml' CACHE_TTL = 7 * 24 * 60 * 60 # 7 days in seconds # Facter runs under Puppet's vendored ruby, whose OpenSSL trusts only # /opt/puppetlabs/puppet/ssl/cert.pem and never the system trust store, so the # estate CA anchor profiles::pki::vaultca installs has to be named explicitly. CA_BUNDLE_PATHS = [ '/etc/pki/ca-trust/source/anchors/vaultcaroot.pem', '/usr/local/share/ca-certificates/vaultcaroot.pem' ].freeze @enc_data = nil # In-memory cache for the ENC response def self.read_cache return {} unless File.exist?(CACHE_FILE) cache_data = YAML.safe_load(File.read(CACHE_FILE)) || {} timestamp = cache_data.fetch('timestamp', 0) return cache_data if Time.now.to_i - timestamp < CACHE_TTL {} end def self.write_cache(enc_data) FileUtils.mkdir_p(File.dirname(CACHE_FILE)) cache_data = enc_data.merge({ 'timestamp' => Time.now.to_i }) File.write(CACHE_FILE, cache_data.to_yaml) end def self.ca_bundle CA_BUNDLE_PATHS.find { |path| File.exist?(path) } end def self.http_client(uri) client = Net::HTTP.new(uri.host, uri.port) client.use_ssl = true client.verify_mode = OpenSSL::SSL::VERIFY_PEER bundle = ca_bundle client.ca_file = bundle if bundle client end def self.fetch_from_encapi uri = URI("https://encapi.k8s.syd1.au.unkin.net/cblr/svc/op/puppet/hostname/#{Facter.value(:fqdn) || Facter.value(:hostname)}") response = http_client(uri).request(Net::HTTP::Get.new(uri)) raise "Failed to fetch ENC data. HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess) YAML.safe_load(response.body) || {} end def self.retrieve_enc_data return @enc_data if @enc_data @enc_data = fetch_from_encapi write_cache(@enc_data) @enc_data end def self.fetch_enc_data retrieve_enc_data rescue StandardError => e Facter.warn("Error retrieving encapi ENC data: #{e.message}") @enc_data = read_cache return @enc_data unless @enc_data.empty? raise 'No cached ENC data available and encapi is unreachable.' end def self.enc_role fetch_enc_data.fetch('classes', {}).keys.first || raise('ENC Role not found in encapi ENC response') end def self.enc_env fetch_enc_data.fetch('environment', nil) || raise('ENC Environment not found in encapi ENC response') end end Facter.add('enc_role') do setcode { EncapiENC.enc_role } end Facter.add('enc_env') do setcode { EncapiENC.enc_env } end