├── features ├── sandbox │ ├── puppet │ │ ├── public │ │ │ └── .gitignore │ │ ├── manifests │ │ │ └── init.pp │ │ ├── puppet.conf │ │ ├── environments │ │ │ └── local │ │ │ │ ├── environment.conf │ │ │ │ ├── modules │ │ │ │ └── test │ │ │ │ │ └── manifests │ │ │ │ │ └── run.pp │ │ │ │ └── test.eyaml │ │ ├── hiera.yaml │ │ └── keys │ │ │ ├── public_key.pkcs7.pem │ │ │ └── private_key.pkcs7.pem │ ├── clean_home │ │ ├── .puppetlabs │ │ │ ├── etc │ │ │ │ └── placeholder │ │ │ ├── var │ │ │ │ └── placeholder │ │ │ └── opt │ │ │ │ └── puppet │ │ │ │ └── public │ │ │ │ └── placeholder │ │ └── README.md │ ├── path │ │ ├── editor.sh │ │ └── spaced editor.sh │ ├── test_plain.yaml │ ├── test_input.txt │ ├── puppet-envvar │ │ ├── manifests │ │ │ └── init.pp │ │ ├── environments │ │ │ └── local │ │ │ │ ├── environment.conf │ │ │ │ ├── modules │ │ │ │ └── test │ │ │ │ │ └── manifests │ │ │ │ │ └── run.pp │ │ │ │ └── test.eyaml │ │ ├── puppet.conf │ │ ├── hiera.yaml │ │ └── keys │ │ │ ├── public_key.pkcs7.pem │ │ │ └── private_key.pkcs7.pem │ ├── puppet-hiera-merge │ │ ├── manifests │ │ │ └── init.pp │ │ ├── environments │ │ │ └── local │ │ │ │ ├── environment.conf │ │ │ │ ├── modules │ │ │ │ └── test │ │ │ │ │ └── manifests │ │ │ │ │ └── run.pp │ │ │ │ ├── test.eyaml │ │ │ │ └── city │ │ │ │ └── test.eyaml │ │ ├── puppet.conf │ │ ├── hiera.yaml │ │ └── keys │ │ │ ├── public_key.pkcs7.pem │ │ │ └── private_key.pkcs7.pem │ ├── test_new_values.yaml │ ├── append.sh │ ├── test_input.bin │ ├── convert_decrypted_values_to_uppercase.sh │ ├── fake_home │ │ └── .eyaml │ │ │ └── config.yaml │ ├── eyaml_config_file.yaml │ ├── test_plain_with_index.yaml │ ├── test_input.encrypted.txt │ ├── test_edit.yaml │ ├── keys │ │ ├── public_key.pkcs7.pem │ │ └── private_key.pkcs7.pem │ ├── test_multiline_input.eyaml │ ├── test_multiline_win_input.eyaml │ └── test_input.yaml ├── support │ ├── puppet.rb │ ├── setup_sandbox.rb │ ├── channels.rb │ └── env.rb ├── step_definitions │ ├── decrypt_steps.rb │ ├── environment_overrides.rb │ ├── recrypt_steps.rb │ └── parser_steps.rb ├── plugin_api.feature ├── valid_encryption.feature ├── config.feature ├── outputs.feature ├── encrypts.feature ├── keys.feature ├── recrypt.feature ├── plugin.feature ├── parser.feature ├── puppet.feature ├── decrypts.feature └── edit.feature ├── sublime_text ├── eyaml.sublime-package ├── README.md └── eyaml.syntax_definition.json ├── PLUGINS.md ├── .rubocop.yml ├── lib └── hiera │ └── backend │ ├── eyaml │ ├── commands.rb │ ├── highlinehelper.rb │ ├── subcommands │ │ ├── createkeys.rb │ │ ├── version.rb │ │ ├── unknown_command.rb │ │ ├── help.rb │ │ ├── recrypt.rb │ │ ├── decrypt.rb │ │ ├── encrypt.rb │ │ └── edit.rb │ ├── options.rb │ ├── encrypthelper.rb │ ├── parser │ │ ├── token.rb │ │ ├── parser.rb │ │ └── encrypted_tokens.rb │ ├── CLI.rb │ ├── plugins.rb │ ├── utils.rb │ ├── encryptor.rb │ ├── logginghelper.rb │ ├── edithelper.rb │ ├── encryptors │ │ └── pkcs7.rb │ └── subcommand.rb │ ├── eyaml.rb │ └── eyaml_backend.rb ├── .gitignore ├── .github ├── dependabot.yml ├── release.yml └── workflows │ ├── test.yml │ └── release.yml ├── bin └── eyaml ├── LICENSE.txt ├── hiera-eyaml.gemspec ├── Gemfile ├── Rakefile ├── HISTORY.md └── .rubocop_todo.yml /features/sandbox/puppet/public/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/sandbox/clean_home/.puppetlabs/etc/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/sandbox/clean_home/.puppetlabs/var/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/sandbox/path/editor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo \"$0\" $* 3 | -------------------------------------------------------------------------------- /features/sandbox/clean_home/.puppetlabs/opt/puppet/public/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/sandbox/path/spaced editor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo \"$0\" $* 3 | -------------------------------------------------------------------------------- /features/sandbox/test_plain.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | key: DEC::PKCS7[value to encrypt]! -------------------------------------------------------------------------------- /features/sandbox/test_input.txt: -------------------------------------------------------------------------------- 1 | The quick brown 2 | fox jumped over 3 | the lazy dog -------------------------------------------------------------------------------- /features/sandbox/puppet/manifests/init.pp: -------------------------------------------------------------------------------- 1 | node "localhost" { 2 | include test::run 3 | } -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/manifests/init.pp: -------------------------------------------------------------------------------- 1 | node "localhost" { 2 | include test::run 3 | } -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/manifests/init.pp: -------------------------------------------------------------------------------- 1 | node "localhost" { 2 | include test::run 3 | } -------------------------------------------------------------------------------- /features/sandbox/test_new_values.yaml: -------------------------------------------------------------------------------- 1 | new_key1: DEC::PKCS7[new value one]! 2 | new_key2: DEC::PKCS7[new value two]! -------------------------------------------------------------------------------- /features/sandbox/append.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | TMPFILE=`mktemp` 3 | cat $2 $1 > $TMPFILE 4 | cp $TMPFILE $2 5 | rm $TMPFILE 6 | -------------------------------------------------------------------------------- /features/sandbox/test_input.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voxpupuli/hiera-eyaml/master/features/sandbox/test_input.bin -------------------------------------------------------------------------------- /sublime_text/eyaml.sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voxpupuli/hiera-eyaml/master/sublime_text/eyaml.sublime-package -------------------------------------------------------------------------------- /features/sandbox/convert_decrypted_values_to_uppercase.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | perl -pi -e 's/(DEC\(\d+\)::[A-Z0-9]+\[.*?\]\!)/uc($1)/ge' $1 3 | -------------------------------------------------------------------------------- /features/support/puppet.rb: -------------------------------------------------------------------------------- 1 | Given(/^I set FACTER_(.*?) to "(.*?)"$/) do |facter, value| 2 | set_environment_variable "FACTER_#{facter}", value 3 | end 4 | -------------------------------------------------------------------------------- /features/sandbox/fake_home/.eyaml/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | pkcs7_public_key: overriden_pub_key.pkcs7.pem 3 | encrypt_method: plaintext 4 | plaintext_diagnostic_message: different -------------------------------------------------------------------------------- /features/sandbox/eyaml_config_file.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | pkcs7_public_key: config_file_pub_key.pkcs7.pem 3 | encrypt_method: plaintext 4 | plaintext_diagnostic_message: config_file_success -------------------------------------------------------------------------------- /features/sandbox/test_plain_with_index.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | key: DEC(23)::PKCS7[value to encrypt]! 3 | 4 | block: > 5 | DEC(24)::PKCS7[block of values 6 | to encrypt on multiple 7 | lines]! 8 | -------------------------------------------------------------------------------- /PLUGINS.md: -------------------------------------------------------------------------------- 1 | PLUGINS 2 | ======= 3 | 4 | Take a look at the skeleton project hiera-eyaml-plaintext, for a bare-bones demo plugin that you can copy and make into your own encryption plugin for hiera-eyaml. 5 | -------------------------------------------------------------------------------- /features/sandbox/clean_home/README.md: -------------------------------------------------------------------------------- 1 | A clean home directory 2 | ====================== 3 | 4 | This directory exists to ensure that during test runs we do not accidentally pick up a user's configuration file. -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | inherit_from: .rubocop_todo.yml 3 | 4 | inherit_gem: 5 | voxpupuli-rubocop: rubocop.yml 6 | 7 | Metrics: 8 | Enabled: false 9 | 10 | Style/IfUnlessModifier: 11 | Enabled: false 12 | -------------------------------------------------------------------------------- /features/sandbox/puppet/puppet.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | vardir=puppet 3 | ssldir=$vardir/ssl 4 | logdir=$vardir/logs 5 | pidfile=$vardir/puppet.pid 6 | hiera_config=$vardir/hiera.yaml 7 | environmentpath=$vardir/environments 8 | environment=local 9 | -------------------------------------------------------------------------------- /features/sandbox/puppet/environments/local/environment.conf: -------------------------------------------------------------------------------- 1 | # This is configuration for the puppet master 2 | # It tells the puppet master how to use the contents of this repo 3 | 4 | modulepath = modules 5 | manifest = manifests/init.pp 6 | 7 | -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/environments/local/environment.conf: -------------------------------------------------------------------------------- 1 | # This is configuration for the puppet master 2 | # It tells the puppet master how to use the contents of this repo 3 | 4 | modulepath = modules 5 | manifest = manifests/init.pp 6 | 7 | -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/puppet.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | vardir=puppet-envvar 3 | ssldir=$vardir/ssl 4 | logdir=$vardir/logs 5 | pidfile=$vardir/puppet.pid 6 | hiera_config=$vardir/hiera.yaml 7 | environmentpath=$vardir/environments 8 | environment=local 9 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/environments/local/environment.conf: -------------------------------------------------------------------------------- 1 | # This is configuration for the puppet master 2 | # It tells the puppet master how to use the contents of this repo 3 | 4 | modulepath = modules 5 | manifest = manifests/init.pp 6 | 7 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/puppet.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | vardir=puppet-hiera-merge 3 | ssldir=$vardir/ssl 4 | logdir=$vardir/logs 5 | pidfile=$vardir/puppet.pid 6 | hiera_config=$vardir/hiera.yaml 7 | environmentpath=$vardir/environments 8 | environment=local 9 | -------------------------------------------------------------------------------- /features/step_definitions/decrypt_steps.rb: -------------------------------------------------------------------------------- 1 | Then(/the output should have a key of '(.*?)' with ([0-9]*?) lines/) do |key, lines| 2 | require 'yaml' 3 | # puts all_output 4 | data = YAML.load(all_output) 5 | expect(data[key]).not_to be_nil 6 | expect(data[key].scan(/[\r\n]+/).size).to eq(lines.to_i) 7 | end 8 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/commands.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | class Hiera 4 | module Backend 5 | module Eyaml 6 | class Commands 7 | @@commands = [] 8 | 9 | def self.register; end 10 | 11 | def self.commands 12 | @@commands 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.gradle 4 | keys/*.pem 5 | pkg/ 6 | tmp/ 7 | .DS_Store 8 | .rvmrc 9 | .ruby-version 10 | .ruby-gemset 11 | Gemfile.lock 12 | .*.sw? 13 | vendor/ 14 | .bundle/ 15 | features/sandbox/puppet-hiera-merge/reports 16 | features/sandbox/puppet-hiera-merge/state 17 | features/sandbox/puppet/reports 18 | features/sandbox/puppet/state 19 | .vendor/ 20 | -------------------------------------------------------------------------------- /features/sandbox/puppet/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | :backends: 3 | - eyaml 4 | - yaml 5 | 6 | :hierarchy: 7 | - environments/%{environment}/%{module_name} 8 | 9 | :yaml: 10 | :datadir: ./puppet 11 | 12 | :logger: console 13 | 14 | :eyaml: 15 | :datadir: ./puppet 16 | :pkcs7_private_key: ./keys/private_key.pkcs7.pem 17 | :pkcs7_public_key: ./keys/public_key.pkcs7.pem 18 | -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | :backends: 3 | - eyaml 4 | - yaml 5 | 6 | :hierarchy: 7 | - environments/%{environment}/%{module_name} 8 | 9 | :yaml: 10 | :datadir: ./puppet-envvar 11 | 12 | :logger: console 13 | 14 | :eyaml: 15 | :datadir: ./puppet-envvar 16 | :pkcs7_private_key_env_var: EYAML_PRIVATE_KEY 17 | :pkcs7_public_key_env_var: EYAML_PUBLIC_KEY 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # raise PRs for gem updates 4 | - package-ecosystem: bundler 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "13:00" 9 | open-pull-requests-limit: 10 10 | 11 | # Maintain dependencies for GitHub Actions 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | time: "13:00" 17 | open-pull-requests-limit: 10 18 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | :backends: 3 | - eyaml 4 | - yaml 5 | 6 | :hierarchy: 7 | - environments/%{environment}/%{fact}/%{module_name} 8 | - environments/%{environment}/%{module_name} 9 | 10 | :yaml: 11 | :datadir: ./puppet-hiera-merge 12 | 13 | :logger: console 14 | :merge_behavior: deeper 15 | :eyaml: 16 | :datadir: ./puppet-hiera-merge 17 | :pkcs7_private_key: ./keys/private_key.pkcs7.pem 18 | :pkcs7_public_key: ./keys/public_key.pkcs7.pem 19 | -------------------------------------------------------------------------------- /features/sandbox/test_input.encrypted.txt: -------------------------------------------------------------------------------- 1 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAgO8TSTj+8D1EyN7T1p2a7YqlXLQdNkr7xfV8DU3foG5x1wXOCVNvANrMNzyCoc8zAeHywhzvG60DaDKMcd/1TIiySpt912rYCWJDcGV0YlfsBDpJTvQlU7ygWoHgDzvtX0Eo1ChCh4TZUmllU96da+6e2c/3/45OyDBiHWy/OZzYzNcA8fx2bqdq+Ucy/WAfvSK8aAVHJu1B2AnXECyopyMgly14j6rAW4dDzsnh9oge+iMsC9uLhqykTMR8q9wo+W092g+rzms8V5CprTGFVLl2Kq/V3o7ItnOqiPURXPxwykCrW8NHr591sWxlXtY1gf1f6u++Fa4UJbEMt7iZkTBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBb5+JHGoxKxM3KQ09F8pyTgCBzK1YAZ5ceYNwW2ybSGqt9x8jfWHbM/jzbCnNOLnAU2Q==] -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/highlinehelper.rb: -------------------------------------------------------------------------------- 1 | require 'highline' 2 | 3 | class Hiera 4 | module Backend 5 | module Eyaml 6 | class HighlineHelper 7 | def self.cli 8 | HighLine.new($stdin, $stderr) 9 | end 10 | 11 | def self.read_password 12 | cli.ask('Enter password: ') { |q| q.echo = '*' } 13 | end 14 | 15 | def self.confirm?(message) 16 | result = cli.ask("#{message} (y/N): ") 17 | %w[y yes].include?(result.downcase) || false 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /sublime_text/README.md: -------------------------------------------------------------------------------- 1 | Sublime Text Syntax Highlighting Package 2 | ======================================== 3 | 4 | The contents of this directory give syntax highlighting to .eyaml files in Sublime Text 2+ 5 | 6 | Install 7 | ======= 8 | 9 | To install, simply copy eyaml.sublime-package into your "Installed Packages" directory in the data directory of your Sublime Text 2 installation. The data directory is: 10 | 11 | * Windows: %APPDATA%/Sublime Text 2 12 | * OS X: ~/Library/Application Support/Sublime Text 2 13 | * Linux: ~/.Sublime Text 2 14 | 15 | Then restart sublimetext 16 | 17 | -------------------------------------------------------------------------------- /features/sandbox/test_edit.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | simple_string: how do you do 3 | encrypted_string: ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAgld+rftjW8WmMwTJLX/3Kk9hQv9ZUufsieijxhnCo3gtR/6xaKdMC4wpYM9Eck7FFdmjz2XnJK9o5rlvjW5ZBH3u2A3tphs6cgy7HzsfrsJvw1Mc+CLSNL35MVi/YvNCxezn+rXn28NW8NntByoLTzZnd6iGxSBk4S7Z7XwvdQWuUjXy0muEeAUYtS/eppNZYdyeMpzE9oHmfMM+zwdOYzc/nfwvnoLHGP+sv6KmnzCyNtqyrdvCIn+m+ljPWpGvj410Q52Xili1Scgi+ALJf4xiEnD5c5YjEkYY8uUe4etCDYZ/aXp9RGvZiHD8Le6jz34fcWbLZlQacCfgcyY8AzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBD4CRz8QLvbtgRx/NTxEnpfgCBLQD1ei8KAcd0LTT7sezZPt6LQnLxPuwx5StflI5xOgA==] 4 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/createkeys.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/subcommand' 2 | 3 | class Hiera 4 | module Backend 5 | module Eyaml 6 | module Subcommands 7 | class Createkeys < Subcommand 8 | def self.options 9 | [] 10 | end 11 | 12 | def self.description 13 | 'create a set of keys with which to encrypt/decrypt eyaml data' 14 | end 15 | 16 | def self.execute 17 | encryptor = Encryptor.find Eyaml.default_encryption_scheme 18 | encryptor.create_keys 19 | nil 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /features/support/setup_sandbox.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | class SetupSandbox 4 | def self.create_files(destdir, test_files) 5 | test_files.each do |test_file, contents| 6 | test_file = File.join(destdir, test_file) 7 | extension = test_file.split('.').last 8 | target_dir = File.dirname(test_file) 9 | FileUtils.mkdir_p(target_dir) unless File.directory?(target_dir) 10 | write_mode = 'w' 11 | write_mode = 'wb' if extension == 'bin' 12 | unless File.exist?(test_file) 13 | File.open(test_file, write_mode) do |input_file| 14 | input_file.puts contents 15 | end 16 | end 17 | File.chmod(0o755, test_file) if extension == 'sh' 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /features/plugin_api.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml plugin api 2 | 3 | In order to develop new encryption plugins for eyaml 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to exercise the encryption plugins in various ways 6 | 7 | Scenario: verify plugin options are available in eyaml 8 | When I run `eyaml createkeys --help` 9 | Then the output should match /plaintext-diagnostic-message/ 10 | And the output should match /pkcs7-private-key/ 11 | And the output should match /pkcs7-public-key/ 12 | 13 | Scenario: exercise plugin options for a plugin 14 | When I run `eyaml createkeys -n plaintext --plaintext-diagnostic-message marker12345` 15 | Then the output should match /Create_keys: marker12345/ 16 | 17 | -------------------------------------------------------------------------------- /bin/eyaml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'hiera/backend/eyaml/CLI' 5 | require 'hiera/backend/eyaml/plugins' 6 | require 'hiera/backend/eyaml/encryptors/pkcs7' 7 | 8 | # Register all plugins 9 | Hiera::Backend::Eyaml::Encryptors::Pkcs7.register 10 | Hiera::Backend::Eyaml::Plugins.find 11 | 12 | begin 13 | Hiera::Backend::Eyaml::CLI.parse 14 | rescue StandardError => e 15 | Hiera::Backend::Eyaml::LoggingHelper.warn e.message 16 | Hiera::Backend::Eyaml::LoggingHelper.debug e.backtrace.join("\n") 17 | exit 1 18 | end 19 | 20 | begin 21 | Hiera::Backend::Eyaml::CLI.execute 22 | rescue StandardError => e 23 | Hiera::Backend::Eyaml::LoggingHelper.warn e.message 24 | Hiera::Backend::Eyaml::LoggingHelper.debug e.backtrace.join("\n") 25 | exit 1 26 | end 27 | -------------------------------------------------------------------------------- /features/step_definitions/environment_overrides.rb: -------------------------------------------------------------------------------- 1 | Given(/^my EDITOR is set to "(.*?)"$/) do |editor_command| 2 | set_environment_variable 'EDITOR', editor_command 3 | end 4 | 5 | Given(/^my HOME is set to "(.*?)"$/) do |home_dir| 6 | # HOME must be absolute 7 | set_environment_variable 'HOME', expand_path(home_dir) 8 | end 9 | 10 | Given(/^my EYAML_CONFIG is set to "(.*?)"$/) do |config_file| 11 | set_environment_variable 'EYAML_CONFIG', config_file 12 | end 13 | 14 | Given(/^my PATH contains "(.*?)"$/) do |path_value| 15 | abspath = expand_path(path_value) 16 | return if ENV['PATH'].start_with? abspath 17 | 18 | paths = [path_value] + ENV['PATH'].split(File::PATH_SEPARATOR) 19 | ENV['PATH'] = paths.join(File::PATH_SEPARATOR) 20 | prepend_environment_variable 'PATH', abspath + File::PATH_SEPARATOR 21 | end 22 | -------------------------------------------------------------------------------- /features/sandbox/puppet/environments/local/modules/test/manifests/run.pp: -------------------------------------------------------------------------------- 1 | class test::run { 2 | 3 | file { "/tmp/eyaml_puppettest.1": 4 | ensure => present, 5 | content => hiera("plaintext_string"), 6 | } 7 | 8 | file { "/tmp/eyaml_puppettest.2": 9 | ensure => present, 10 | content => hiera("encrypted_string"), 11 | } 12 | 13 | # Ugly hack to call hiera() from puppet >= 4 14 | file { "/tmp/eyaml_puppettest.3": 15 | ensure => present, 16 | content => inline_template("<%= scope.compiler.loaders.private_environment_loader.load(:function,'hiera').call(scope, 'encrypted_string') %>"), 17 | } 18 | 19 | file { "/tmp/eyaml_puppettest.4": 20 | ensure => present, 21 | content => hiera("default_encrypted_string"), 22 | } 23 | 24 | file { "/tmp/eyaml_puppettest.5": 25 | ensure => present, 26 | content => hiera("encrypted_block"), 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/environments/local/modules/test/manifests/run.pp: -------------------------------------------------------------------------------- 1 | class test::run { 2 | 3 | file { "/tmp/eyaml_puppettest.1": 4 | ensure => present, 5 | content => hiera("plaintext_string"), 6 | } 7 | 8 | file { "/tmp/eyaml_puppettest.2": 9 | ensure => present, 10 | content => hiera("encrypted_string"), 11 | } 12 | 13 | # Ugly hack to call hiera() from puppet >= 4 14 | file { "/tmp/eyaml_puppettest.3": 15 | ensure => present, 16 | content => inline_template("<%= scope.compiler.loaders.private_environment_loader.load(:function,'hiera').call(scope, 'encrypted_string') %>"), 17 | } 18 | 19 | file { "/tmp/eyaml_puppettest.4": 20 | ensure => present, 21 | content => hiera("default_encrypted_string"), 22 | } 23 | 24 | file { "/tmp/eyaml_puppettest.5": 25 | ensure => present, 26 | content => hiera("encrypted_block"), 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 3 | 4 | changelog: 5 | exclude: 6 | labels: 7 | - duplicate 8 | - invalid 9 | - modulesync 10 | - question 11 | - skip-changelog 12 | - wont-fix 13 | - wontfix 14 | - github_actions 15 | 16 | categories: 17 | - title: Breaking Changes 🛠 18 | labels: 19 | - backwards-incompatible 20 | 21 | - title: New Features 🎉 22 | labels: 23 | - enhancement 24 | 25 | - title: Bug Fixes 🐛 26 | labels: 27 | - bug 28 | - bugfix 29 | 30 | - title: Documentation Updates 📚 31 | labels: 32 | - documentation 33 | - docs 34 | 35 | - title: Dependency Updates ⬆️ 36 | labels: 37 | - dependencies 38 | 39 | - title: Other Changes 40 | labels: 41 | - "*" 42 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/environments/local/modules/test/manifests/run.pp: -------------------------------------------------------------------------------- 1 | class test::run { 2 | 3 | $data = hiera_hash($title) 4 | 5 | File { backup => false } 6 | 7 | file { "/tmp/eyaml_puppettest.1": 8 | ensure => present, 9 | content => $data['plaintext_string'], 10 | } 11 | 12 | file { "/tmp/eyaml_puppettest.2": 13 | ensure => present, 14 | content => $data['encrypted_string'], 15 | } 16 | 17 | file { "/tmp/eyaml_puppettest.3": 18 | ensure => present, 19 | content => inline_template("<%= require 'json'; JSON.pretty_generate @data['encrypted_array'] %>\n"), 20 | } 21 | 22 | file { "/tmp/eyaml_puppettest.4": 23 | ensure => present, 24 | content => inline_template("<%= require 'json'; JSON.pretty_generate @data['encrypted_hash'] %>\n"), 25 | } 26 | 27 | file { "/tmp/eyaml_puppettest.5": 28 | ensure => present, 29 | content => $data['encrypted_block'], 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /features/sandbox/keys/public_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC2TCCAcGgAwIBAgIBADANBgkqhkiG9w0BAQUFADAAMCAXDTEzMDgwMTE3MzU0 3 | NVoYDzIwNjMwNzIwMTczNTQ1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 4 | CgKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQqErelZUhE 5 | i2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK1bwD7fCW 6 | XrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAEbBKyRRHH 7 | JzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeRb7FCyOPa 8 | LRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvfBr42sp0K 9 | R7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ 10 | MB0GA1UdDgQWBBR7PyZZ/WlaSjAf6GO2GLWXO5aINDAoBgNVHSMEITAfgBR7PyZZ 11 | /WlaSjAf6GO2GLWXO5aINKEEpAIwAIIBADANBgkqhkiG9w0BAQUFAAOCAQEAspcX 12 | VNb156OZqPxteosI2ijeewDH0sc3ogRDZnxbXqG6Pa44QzJNTUULaX3iEfmFw4TL 13 | MW90NU4w4rzSIBGDGHTBOKZZBa2iTVRsYHuQ7Sd1A1MKc1dDFs9+Uj/6/vQ8Fkdx 14 | v+iei5N/XFccgto4HiohXvEZhT4NjONIFR9UL+lWChp8OVb+ifOWCKF69iUUbMPC 15 | uD7+UkrCYoVeVXkG1Rvw4E6BAbi75P6inX0lUnzNs4B0rvFynnV1Nsdb89cxI3pL 16 | Q0yVE8/aIP9donqjXTu0h/GmOtVQH654NCQSs1dLD+K4+8mn591Nv4uLrCiNJzTM 17 | zGRUV6zOPlUa3ye6Dg== 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/version.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/subcommand' 2 | require 'hiera/backend/eyaml' 3 | 4 | class Hiera 5 | module Backend 6 | module Eyaml 7 | module Subcommands 8 | class Version < Subcommand 9 | def self.options 10 | [] 11 | end 12 | 13 | def self.description 14 | 'show version information' 15 | end 16 | 17 | def self.execute 18 | Eyaml::LoggingHelper.info "hiera-eyaml (core): #{Eyaml::VERSION}" 19 | 20 | Plugins.plugins.each do |plugin| 21 | plugin_shortname = plugin.name.split('hiera-eyaml-').last 22 | plugin_version = begin 23 | Encryptor.find(plugin_shortname)::VERSION.to_s 24 | rescue StandardError 25 | 'unknown (is plugin compatible with eyaml 2.0+ ?)' 26 | end 27 | Eyaml::LoggingHelper.info "hiera-eyaml-#{plugin_shortname} (gem): #{plugin_version}" 28 | end 29 | 30 | nil 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /features/sandbox/puppet/keys/public_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC2TCCAcGgAwIBAgIBADANBgkqhkiG9w0BAQUFADAAMCAXDTEzMDgwMTE3MzU0 3 | NVoYDzIwNjMwNzIwMTczNTQ1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 4 | CgKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQqErelZUhE 5 | i2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK1bwD7fCW 6 | XrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAEbBKyRRHH 7 | JzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeRb7FCyOPa 8 | LRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvfBr42sp0K 9 | R7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ 10 | MB0GA1UdDgQWBBR7PyZZ/WlaSjAf6GO2GLWXO5aINDAoBgNVHSMEITAfgBR7PyZZ 11 | /WlaSjAf6GO2GLWXO5aINKEEpAIwAIIBADANBgkqhkiG9w0BAQUFAAOCAQEAspcX 12 | VNb156OZqPxteosI2ijeewDH0sc3ogRDZnxbXqG6Pa44QzJNTUULaX3iEfmFw4TL 13 | MW90NU4w4rzSIBGDGHTBOKZZBa2iTVRsYHuQ7Sd1A1MKc1dDFs9+Uj/6/vQ8Fkdx 14 | v+iei5N/XFccgto4HiohXvEZhT4NjONIFR9UL+lWChp8OVb+ifOWCKF69iUUbMPC 15 | uD7+UkrCYoVeVXkG1Rvw4E6BAbi75P6inX0lUnzNs4B0rvFynnV1Nsdb89cxI3pL 16 | Q0yVE8/aIP9donqjXTu0h/GmOtVQH654NCQSs1dLD+K4+8mn591Nv4uLrCiNJzTM 17 | zGRUV6zOPlUa3ye6Dg== 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/keys/public_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC2TCCAcGgAwIBAgIBADANBgkqhkiG9w0BAQUFADAAMCAXDTEzMDgwMTE3MzU0 3 | NVoYDzIwNjMwNzIwMTczNTQ1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 4 | CgKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQqErelZUhE 5 | i2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK1bwD7fCW 6 | XrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAEbBKyRRHH 7 | JzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeRb7FCyOPa 8 | LRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvfBr42sp0K 9 | R7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ 10 | MB0GA1UdDgQWBBR7PyZZ/WlaSjAf6GO2GLWXO5aINDAoBgNVHSMEITAfgBR7PyZZ 11 | /WlaSjAf6GO2GLWXO5aINKEEpAIwAIIBADANBgkqhkiG9w0BAQUFAAOCAQEAspcX 12 | VNb156OZqPxteosI2ijeewDH0sc3ogRDZnxbXqG6Pa44QzJNTUULaX3iEfmFw4TL 13 | MW90NU4w4rzSIBGDGHTBOKZZBa2iTVRsYHuQ7Sd1A1MKc1dDFs9+Uj/6/vQ8Fkdx 14 | v+iei5N/XFccgto4HiohXvEZhT4NjONIFR9UL+lWChp8OVb+ifOWCKF69iUUbMPC 15 | uD7+UkrCYoVeVXkG1Rvw4E6BAbi75P6inX0lUnzNs4B0rvFynnV1Nsdb89cxI3pL 16 | Q0yVE8/aIP9donqjXTu0h/GmOtVQH654NCQSs1dLD+K4+8mn591Nv4uLrCiNJzTM 17 | zGRUV6zOPlUa3ye6Dg== 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Tom Poulton 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/keys/public_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC2TCCAcGgAwIBAgIBADANBgkqhkiG9w0BAQUFADAAMCAXDTEzMDgwMTE3MzU0 3 | NVoYDzIwNjMwNzIwMTczNTQ1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 4 | CgKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQqErelZUhE 5 | i2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK1bwD7fCW 6 | XrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAEbBKyRRHH 7 | JzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeRb7FCyOPa 8 | LRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvfBr42sp0K 9 | R7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ 10 | MB0GA1UdDgQWBBR7PyZZ/WlaSjAf6GO2GLWXO5aINDAoBgNVHSMEITAfgBR7PyZZ 11 | /WlaSjAf6GO2GLWXO5aINKEEpAIwAIIBADANBgkqhkiG9w0BAQUFAAOCAQEAspcX 12 | VNb156OZqPxteosI2ijeewDH0sc3ogRDZnxbXqG6Pa44QzJNTUULaX3iEfmFw4TL 13 | MW90NU4w4rzSIBGDGHTBOKZZBa2iTVRsYHuQ7Sd1A1MKc1dDFs9+Uj/6/vQ8Fkdx 14 | v+iei5N/XFccgto4HiohXvEZhT4NjONIFR9UL+lWChp8OVb+ifOWCKF69iUUbMPC 15 | uD7+UkrCYoVeVXkG1Rvw4E6BAbi75P6inX0lUnzNs4B0rvFynnV1Nsdb89cxI3pL 16 | Q0yVE8/aIP9donqjXTu0h/GmOtVQH654NCQSs1dLD+K4+8mn591Nv4uLrCiNJzTM 17 | zGRUV6zOPlUa3ye6Dg== 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /features/valid_encryption.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml encrypting is valid 2 | 3 | Scenario: encrypt and decrypt a binary file 4 | When I run `bash -c "eyaml encrypt -o string -f test_input.bin > test_output.txt"` 5 | When I run `bash -c "eyaml decrypt -f test_output.txt > test_output.bin"` 6 | When I run `file test_output.bin` 7 | Then the output should match /PNG image data/ 8 | 9 | Scenario: encrypt and decrypt a simple file 10 | When I run `bash -c "eyaml encrypt -o string -f test_input.txt > test_output.txt"` 11 | When I run `eyaml decrypt -f test_output.txt` 12 | Then the output should match /fox jumped over/ 13 | 14 | Scenario: encrypt and decrypt multiline yaml 15 | When I run `eyaml decrypt -f test_multiline_input.eyaml` 16 | Then the output should have a key of 'key' with 3 lines 17 | Then the output should have a key of 'theotherkey' with 3 lines 18 | 19 | Scenario: encrypt and decrypt windows multiline yaml 20 | When I run `eyaml decrypt -f test_multiline_win_input.eyaml` 21 | Then the output should have a key of 'key' with 3 lines 22 | Then the output should have a key of 'theotherkey' with 3 lines 23 | 24 | -------------------------------------------------------------------------------- /hiera-eyaml.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'hiera/backend/eyaml' 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = 'hiera-eyaml' 7 | gem.version = Hiera::Backend::Eyaml::VERSION 8 | gem.description = 'Hiera backend for decrypting encrypted yaml properties' 9 | gem.summary = 'OpenSSL Encryption backend for Hiera' 10 | gem.author = 'Vox Pupuli' 11 | gem.email = 'voxpupuli@groups.io' 12 | gem.license = 'MIT' 13 | 14 | gem.homepage = 'https://github.com/voxpupuli/hiera-eyaml/' 15 | gem.files = `git ls-files`.split($/).reject { |file| file =~ /^features.*$/ } 16 | gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } 17 | gem.require_paths = ['lib'] 18 | 19 | gem.add_dependency 'highline', '>= 2.1', '< 4' 20 | gem.add_dependency 'optimist', '~> 3.1' 21 | 22 | gem.add_development_dependency 'rake', '~> 13.2', '>= 13.2.1' 23 | gem.add_development_dependency 'rspec-expectations', '~> 3.13' 24 | gem.add_development_dependency 'voxpupuli-rubocop', '~> 3.1.0' 25 | 26 | gem.required_ruby_version = '>= 2.7', ' < 4' 27 | end 28 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml.rb: -------------------------------------------------------------------------------- 1 | class Hiera 2 | module Backend 3 | module Eyaml 4 | VERSION = '4.3.0' 5 | DESCRIPTION = 'Hiera-eyaml is a backend for Hiera which provides OpenSSL encryption/decryption for Hiera properties' 6 | 7 | class RecoverableError < StandardError 8 | end 9 | 10 | def self.subcommand=(command) 11 | @@subcommand = command 12 | end 13 | 14 | def self.subcommand 15 | @@subcommand 16 | end 17 | 18 | def self.default_encryption_scheme=(new_encryption) 19 | @@default_encryption_scheme = new_encryption 20 | end 21 | 22 | def self.default_encryption_scheme 23 | @@default_encryption_scheme ||= 'PKCS7' 24 | @@default_encryption_scheme 25 | end 26 | 27 | def self.verbosity_level=(new_verbosity_level) 28 | @@debug_level = new_verbosity_level 29 | end 30 | 31 | def self.verbosity_level 32 | @@debug_level ||= 1 33 | @@debug_level 34 | end 35 | 36 | def self.subcommands=(commands) 37 | @@subcommands = commands 38 | end 39 | 40 | def self.subcommands 41 | @@subcommands 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/options.rb: -------------------------------------------------------------------------------- 1 | class Hiera 2 | module Backend 3 | module Eyaml 4 | class Options 5 | def self.[]=(key, value) 6 | @@options ||= {} 7 | @@options[key.to_sym] = value 8 | end 9 | 10 | def self.[](key) 11 | @@options ||= {} 12 | @@options[key.to_sym] 13 | end 14 | 15 | def self.set(hash) 16 | @@options = {} 17 | hash.each do |k, v| 18 | @@options[k.to_sym] = v 19 | end 20 | end 21 | 22 | def self.trace 23 | LoggingHelper.trace 'Dump of eyaml tool options dict:' 24 | LoggingHelper.trace '--------------------------------' 25 | @@options.each do |k, v| 26 | LoggingHelper.trace format '%18s %-18s = %18s %-18s', "(#{k.class.name})", k.to_s, "(#{v.class.name})", 27 | v.to_s 28 | rescue StandardError 29 | LoggingHelper.trace format '%18s %-18s = %18s %-18s', "(#{k.class.name})", k.to_s, "(#{v.class.name})", '' # case where v is binary 30 | end 31 | LoggingHelper.trace '--------------------------------' 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/encrypthelper.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'fileutils' 3 | 4 | class Hiera 5 | module Backend 6 | module Eyaml 7 | class EncryptHelper 8 | def self.write_important_file(args) 9 | require 'hiera/backend/eyaml/highlinehelper' 10 | filename = args[:filename] 11 | content = args[:content] 12 | mode = args[:mode] 13 | if File.file?("#{filename}") && !(HighlineHelper.confirm? "Are you sure you want to overwrite \"#{filename}\"?") 14 | raise StandardError, 15 | 'User aborted' 16 | end 17 | open("#{filename}", 'w') do |io| 18 | io.write(content) 19 | end 20 | File.chmod(mode, filename) unless mode.nil? 21 | end 22 | 23 | def self.ensure_key_dir_exists(key_file) 24 | key_dir = File.dirname key_file 25 | 26 | return if File.directory? key_dir 27 | 28 | begin 29 | FileUtils.mkdir_p key_dir 30 | LoggingHelper.info "Created key directory: #{key_dir}" 31 | rescue StandardError 32 | raise StandardError, "Cannot create key directory: #{key_dir}" 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/unknown_command.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/subcommand' 2 | 3 | class Hiera 4 | module Backend 5 | module Eyaml 6 | module Subcommands 7 | class UnknownCommand < Eyaml::Subcommand 8 | class << self 9 | attr_accessor :original_command 10 | end 11 | 12 | @@original_command = 'unknown' 13 | 14 | def self.options 15 | [] 16 | end 17 | 18 | def self.description 19 | "Unknown command (#{@@original_command})" 20 | end 21 | 22 | def self.execute 23 | puts <<~EOS 24 | Unknown subcommand#{': ' + Eyaml.subcommand if Eyaml.subcommand} 25 | 26 | Usage: eyaml 27 | 28 | Please use one of the following subcommands or help for more help: 29 | #{Eyaml.subcommands.sort.collect do |command| 30 | command_class = Subcommands.const_get(Utils.camelcase(command)) 31 | command unless command_class.hidden? 32 | end.compact.join(', ')} 33 | EOS 34 | end 35 | 36 | def self.hidden? 37 | true 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /features/step_definitions/recrypt_steps.rb: -------------------------------------------------------------------------------- 1 | Given(/I recrypt a file/) do 2 | @enc_parser = Hiera::Backend::Eyaml::Parser::ParserFactory.encrypted_parser 3 | @dec_parser = Hiera::Backend::Eyaml::Parser::ParserFactory.decrypted_parser 4 | end 5 | 6 | And(/^I recrypt it twice$/) do 7 | @tokens = @enc_parser.parse @content 8 | @decrypted_content = @tokens.each_with_index.to_a.map { |(t, index)| t.to_decrypted index: index }.join 9 | 10 | @edited_tokens = @dec_parser.parse @decrypted_content 11 | @encrypted_output = @edited_tokens.map { |t| t.to_encrypted }.join 12 | 13 | @tokens_check = @enc_parser.parse @encrypted_output 14 | @decrypted_content_check = @tokens.each_with_index.to_a.map { |(t, index)| t.to_decrypted index: index }.join 15 | end 16 | 17 | Then(/the recrypted tokens should match/) do 18 | expect(@tokens.size).to eq(@tokens_check.size.to_i) 19 | end 20 | 21 | Then(/the recrypted decrypted content should match/) do 22 | @decrypted_content == @decrypted_content_check 23 | end 24 | 25 | Then(/the recrypted contents should differ/) do 26 | @content != @encrypted_output 27 | end 28 | 29 | Then(/^the tokens at (\d+) should match/) do |index| 30 | decrypted1 = @tokens[index.to_i] 31 | decrypted2 = @tokens_check[index.to_i] 32 | expect(decrypted1.to_decrypted).to eq(decrypted2.to_decrypted) 33 | end 34 | -------------------------------------------------------------------------------- /features/config.feature: -------------------------------------------------------------------------------- 1 | Feature: config file overrides 2 | 3 | Scenario: uses default from eyaml when no config file 4 | When I run `eyaml version -t` 5 | Then the output should match /pkcs7_public_key\s+=\s+\(String\)\s+\..keys.public_key\.pkcs7\.pem/ 6 | And the output should match /encrypt_method\s+=\s+\(String\)\s+pkcs7/ 7 | And the output should match /plaintext_diagnostic_message\s+=\s+\(String\)\s+success/ 8 | 9 | Scenario: uses default from configuration file 10 | Given my HOME is set to "fake_home" 11 | When I run `eyaml version -t` 12 | Then the output should match /pkcs7_public_key\s+=\s+\(String\)\s+overriden_pub_key\.pkcs7\.pem/ 13 | And the output should match /encrypt_method\s+=\s+\(String\)\s+plaintext/ 14 | And the output should match /plaintext_diagnostic_message\s+=\s+\(String\)\s+different/ 15 | 16 | Scenario: uses default from configuration file supplied by a environment variable 17 | Given my EYAML_CONFIG is set to "eyaml_config_file.yaml" 18 | When I run `eyaml version -t` 19 | Then the output should match /pkcs7_public_key\s+=\s+\(String\)\s+config_file_pub_key\.pkcs7\.pem/ 20 | And the output should match /encrypt_method\s+=\s+\(String\)\s+plaintext/ 21 | And the output should match /plaintext_diagnostic_message\s+=\s+\(String\)\s+config_file_success/ 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | # Find a location or specific version for a gem. place_or_version can be a 4 | # version, which is most often used. It can also be git, which is specified as 5 | # `git://somewhere.git#branch`. You can also use a file source location, which 6 | # is specified as `file://some/location/on/disk`. 7 | def location_for(place_or_version, fake_version = nil) 8 | if place_or_version =~ /^(https[:@][^#]*)#(.*)/ 9 | [fake_version, { git: Regexp.last_match(1), branch: Regexp.last_match(2), require: false }].compact 10 | elsif place_or_version =~ %r{^file://(.*)} 11 | ['>= 0', { path: File.expand_path(Regexp.last_match(1)), require: false }] 12 | else 13 | [place_or_version, { require: false }] 14 | end 15 | end 16 | 17 | gemspec 18 | 19 | group :development do 20 | gem 'activesupport' 21 | gem 'aruba', '~> 2.2' 22 | gem 'cucumber', '~> 9.2' 23 | gem 'hiera-eyaml-plaintext' 24 | gem 'openvox', *location_for(ENV['OPENVOX_VERSION']) if ENV['OPENVOX_VERSION'] 25 | end 26 | 27 | group :release, optional: true do 28 | gem 'faraday-retry', '~> 2.1', require: false 29 | gem 'github_changelog_generator', '~> 1.16.4', require: false 30 | end 31 | 32 | group :coverage, optional: ENV['COVERAGE'] != 'yes' do 33 | gem 'codecov', require: false 34 | gem 'simplecov-console', require: false 35 | end 36 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/parser/token.rb: -------------------------------------------------------------------------------- 1 | class Hiera 2 | module Backend 3 | module Eyaml 4 | module Parser 5 | class TokenType 6 | attr_reader :regex 7 | 8 | def create_token(_string) 9 | raise 'Abstract method called' 10 | end 11 | end 12 | 13 | class Token 14 | attr_reader :match 15 | 16 | def initialize(match) 17 | @match = match 18 | end 19 | 20 | def to_encrypted(_args = {}) 21 | raise 'Abstract method called' 22 | end 23 | 24 | def to_decrypted(_args = {}) 25 | raise 'Abstract method called' 26 | end 27 | 28 | def to_plain_text 29 | raise 'Abstract method called' 30 | end 31 | 32 | def to_s 33 | "#{self.class.name}:#{@match}" 34 | end 35 | end 36 | 37 | class NonMatchToken < Token 38 | def initialize(non_match) 39 | super 40 | end 41 | 42 | def to_encrypted(_args = {}) 43 | @match 44 | end 45 | 46 | def to_decrypted(_args = {}) 47 | @match 48 | end 49 | 50 | def to_plain_text 51 | @match 52 | end 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /features/outputs.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml outputs 2 | 3 | In order to better understand the link between eyaml and yaml files 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to output encrypted data in various yaml formats 6 | 7 | 8 | Scenario: encrypt a simple string with default output 9 | When I run `eyaml encrypt -s some_string` 10 | Then the output should match /string: ENC\[PKCS7,(.*?)\]/ 11 | And the output should match /block: >\s*\n\s*ENC\[PKCS7,(.*?)\]/ 12 | 13 | Scenario: encrypt a simple string with examples output 14 | When I run `eyaml encrypt -o examples -s some_string` 15 | Then the output should match /string: ENC\[PKCS7,(.*?)\]/ 16 | And the output should match /block: >\s*\n\s*ENC\[PKCS7,(.*?)\]/ 17 | 18 | Scenario: encrypt a simple string with string output 19 | When I run `eyaml encrypt -o string -s some_string` 20 | Then the output should match /^ENC\[PKCS7,(.*?)\]$/ 21 | 22 | Scenario: encrypt a simple string with raw output 23 | When I run `eyaml encrypt -o raw -s some_string` 24 | Then the output should match /^ENC\[PKCS7,(.*?)\]$/ 25 | And the output should contain "\n" 26 | 27 | Scenario: encrypt a simple string with block output 28 | When I run `eyaml encrypt -o block -s some_string` 29 | Then the output should match /^\s+ENC\[PKCS7,(.*?)\]$/ 30 | 31 | -------------------------------------------------------------------------------- /features/support/channels.rb: -------------------------------------------------------------------------------- 1 | # Cucumber does not have built-in support for waiting on stderr; the following 2 | # is a copy of the corresponding "wait for stdout", with stderr substituted for 3 | # stdout. 4 | # https://github.com/cucumber/aruba/blob/0cbd0e826994a67259b242c7befdb956552246e8/lib/aruba/cucumber/command.rb#L116-L129 5 | When('I wait for stderr to contain {string}') do |expected| 6 | Timeout.timeout(aruba.config.exit_timeout) do 7 | loop do 8 | output = last_command_started.stderr wait_for_io: 0 9 | 10 | output = sanitize_text(output) 11 | expected = sanitize_text(expected) 12 | 13 | break if output.include? expected 14 | 15 | sleep 0.1 16 | end 17 | end 18 | end 19 | 20 | # Cucumber does not have built-in support for regex match against stdin or 21 | # stdout (only "output", which inclues both at once). 22 | Then(%r{^the (stdout|stderr) should( not)? match /([^/]*)/$}) do |channel, negated, expected| 23 | matcher = case channel 24 | when 'stderr'; then :have_output_on_stderr 25 | when 'stdout'; then :have_output_on_stdout 26 | end 27 | if negated 28 | expect(all_commands) 29 | .not_to include send(matcher, an_output_string_matching(expected)) 30 | else 31 | expect(all_commands) 32 | .to include send(matcher, an_output_string_matching(expected)) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/help.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/subcommand' 2 | require 'hiera/backend/eyaml' 3 | 4 | class Hiera 5 | module Backend 6 | module Eyaml 7 | module Subcommands 8 | class Help < Subcommand 9 | def self.options 10 | [] 11 | end 12 | 13 | def self.description 14 | 'this page' 15 | end 16 | 17 | def self.execute 18 | puts <<~EOS 19 | Welcome to eyaml #{Eyaml::VERSION}#{' '} 20 | 21 | Usage: 22 | eyaml subcommand [global-opts] [subcommand-opts] 23 | 24 | Available subcommands: 25 | #{Eyaml.subcommands.collect do |command| 26 | command_class = Subcommands.const_get(Utils.camelcase(command)) 27 | format '%15s: %-65s', command.downcase, command_class.description unless command_class.hidden? 28 | end.compact.join("\n")} 29 | 30 | For more help on an individual command, use --help on that command 31 | 32 | Installed Plugins: 33 | #{Plugins.plugins.collect do |plugin| # {' '} 34 | "\t" + plugin.name.split('hiera-eyaml-').last 35 | end.join("\n")} 36 | EOS 37 | end 38 | 39 | def self.hidden? 40 | true 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'simplecov' 3 | require 'simplecov-console' 4 | require 'codecov' 5 | rescue LoadError 6 | else 7 | SimpleCov.start do 8 | track_files 'lib/**/*.rb' 9 | 10 | add_filter '/spec' 11 | 12 | enable_coverage :branch 13 | 14 | # do not track vendored files 15 | add_filter '/vendor' 16 | add_filter '/.vendor' 17 | end 18 | 19 | SimpleCov.formatters = [ 20 | SimpleCov::Formatter::Console, 21 | SimpleCov::Formatter::Codecov, 22 | ] 23 | end 24 | 25 | # https://cucumber.io/docs/tools/ruby/ 26 | # https://stackoverflow.com/questions/6473419/using-simplecov-to-display-cucumber-code-coverage 27 | require 'cucumber/rake/task' 28 | Cucumber::Rake::Task.new(:features) do |t| 29 | t.cucumber_opts = %w[--format progress --strict] # Any valid command line option can go here. 30 | end 31 | 32 | begin 33 | require 'github_changelog_generator/task' 34 | rescue LoadError 35 | # Do nothing if no required gem installed 36 | else 37 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 38 | version = Hiera::Backend::Eyaml::VERSION 39 | config.future_release = "v#{version}" if /^\d+\.\d+.\d+$/.match?(version) 40 | config.header = "# Changelog\n\nAll notable changes to this project will be documented in this file." 41 | config.exclude_labels = %w[duplicate question invalid wontfix wont-fix skip-changelog github_actions] 42 | config.user = 'voxpupuli' 43 | config.project = 'hiera-eyaml' 44 | end 45 | end 46 | 47 | begin 48 | require 'voxpupuli/rubocop/rake' 49 | rescue LoadError 50 | # the voxpupuli-rubocop gem is optional 51 | end 52 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## v2.1.0 (2016-03-02) 2 | 3 | - (#187) - Change the way third party highline library is imported to avoid memory leak when running under puppet server (@petems) 4 | - (#181) - Improve test suite to run against a variety of puppet versions (@peculater) 5 | 6 | ## v2.0.8 (2015-04-15) 7 | 8 | - (#149) - Fix to tempfile permissions and invalid editor scenario (@elyscape) 9 | 10 | ## v2.0.7 (2015-03-04) 11 | 12 | - (#142) - Fixed highline dependency to exclude newer versions that are not compatible with ruby 1.8.7 (@elyscape) 13 | - (#136) - \t and \r characters are now supported in encrypted blocks (@elyscape) 14 | - (#138) - Added missing tags and new tagging tool (@elyscape) 15 | 16 | ## v2.0.6 (2014-12-13) 17 | 18 | - (#131) - Fix another EDITOR bug (#130) that could erase command line flags to the specified editor (@elyscape) 19 | 20 | ## v2.0.5 (2014-12-11) 21 | 22 | - (#128) - Fix a bug (#127) that caused `eyaml edit` to break when `$EDITOR` was a command on PATH rather than a path to a command (@elyscape) 23 | 24 | ## v2.0.4 (2014-11-24) 25 | 26 | - Add change log 27 | - (#118) - Some initial support for spaces in filenames (primarily targeted at windows platforms) (@elyscape) 28 | - (#114) - Add new config file resolution so that a system wide /etc/eyaml/config.yaml is processed first (@gtmtech) 29 | - (#112) - Improve debugging options and colorise output (@gtmtech) 30 | - (#102) - Extension of temp files should be yaml to help editors provide syntax highlighting (@ColinHebert) 31 | - (#90), #121, #122 - Add preamble in edit mode to make it easier to remember how to edit (@sihil) 32 | - (#96), #111, #116 - Various updates to docs 33 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | rubylib = (ENV['RUBYLIB'] || '').split(File::PATH_SEPARATOR) 2 | rubylib.unshift %(#{File.dirname(__FILE__) + '/../../lib'}) 3 | ENV['RUBYLIB'] = rubylib.uniq.join(File::PATH_SEPARATOR) 4 | require 'rubygems' 5 | require 'aruba' 6 | require 'aruba/cucumber' 7 | require 'fileutils' 8 | require 'pathname' 9 | require 'rspec/expectations' 10 | require 'hiera/backend/eyaml/parser/parser' 11 | require 'hiera/backend/eyaml/options' 12 | require 'hiera/backend/eyaml/parser/encrypted_tokens' 13 | 14 | test_files = {} 15 | Dir.glob('features/sandbox/**/*', File::FNM_DOTMATCH).each do |file_name| 16 | next unless File.file? file_name 17 | 18 | 'rb' if /\.bin$/.match?(file_name) 19 | file = File.open(file_name, 'r') 20 | file_contents = file.read 21 | file.close 22 | file_name = file_name.slice(17, file_name.length) 23 | test_files[file_name] = file_contents 24 | end 25 | 26 | Aruba.configure do |config| 27 | # A number of checks require absolute paths. 28 | config.allow_absolute_paths = true 29 | # Setup the test environment. 30 | config.before :command do |_cmd| 31 | SetupSandbox.create_files aruba.config.working_directory, test_files 32 | end 33 | end 34 | 35 | Before do 36 | home_dir = 'clean_home' 37 | # set to a non-existant home in order so rogue configs don't confuse 38 | # set_environment_variable 'HOME', home_dir 39 | ## But it must be an absolute path for other code 40 | # e.g. puppet will throw: "Error: Could not initialize global default settings: non-absolute home" 41 | set_environment_variable 'HOME', expand_path(home_dir) 42 | set_environment_variable 'EYAML_CONFIG', '' 43 | @aruba_timeout_seconds = 30 44 | end 45 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/CLI.rb: -------------------------------------------------------------------------------- 1 | require 'optimist' 2 | require 'hiera/backend/eyaml' 3 | require 'hiera/backend/eyaml/logginghelper' 4 | require 'hiera/backend/eyaml/utils' 5 | require 'hiera/backend/eyaml/plugins' 6 | require 'hiera/backend/eyaml/options' 7 | require 'hiera/backend/eyaml/subcommand' 8 | 9 | class Hiera 10 | module Backend 11 | module Eyaml 12 | class CLI 13 | def self.parse 14 | Utils.require_dir 'hiera/backend/eyaml/subcommands' 15 | Eyaml.subcommands = Utils.find_all_subclasses_of({ parent_class: Hiera::Backend::Eyaml::Subcommands }).collect do |classname| 16 | Utils.snakecase classname 17 | end 18 | 19 | Eyaml.subcommand = ARGV.shift 20 | subcommand = case Eyaml.subcommand 21 | when nil 22 | ARGV.delete_if { true } 23 | 'unknown_command' 24 | when /^-/ 25 | ARGV.delete_if { true } 26 | 'help' 27 | else 28 | Eyaml.subcommand 29 | end 30 | 31 | command_class = Subcommand.find subcommand 32 | 33 | options = command_class.parse 34 | options[:executor] = command_class 35 | 36 | options = command_class.validate options 37 | Eyaml::Options.set options 38 | Eyaml::Options.trace 39 | end 40 | 41 | def self.execute 42 | executor = Eyaml::Options[:executor] 43 | 44 | result = executor.execute 45 | executor.print_out(result) unless result.nil? 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /features/encrypts.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml encrypting 2 | 3 | In order to encrypt data 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to encrypt data in various ways 6 | 7 | Scenario: encrypt a simple string 8 | When I run `eyaml encrypt -o string -s some_string` 9 | Then the output should match /\AENC\[PKCS7,(.*?)\]\z/ 10 | 11 | Scenario: encrypt a simple file 12 | When I run `eyaml encrypt -o string -f test_input.txt` 13 | Then the output should match /\AENC\[PKCS7,(.*?)\]\z/ 14 | 15 | Scenario: encrypt a eyaml file 16 | When I run `eyaml encrypt --eyaml test_plain.yaml` 17 | Then the output should match /key: ENC\[PKCS7,(.*?)\]$/ 18 | 19 | Scenario: encrypt a binary file 20 | When I run `eyaml encrypt -o string -f test_input.bin` 21 | Then the output should match /\AENC\[PKCS7,(.*?)\]\z/ 22 | 23 | Scenario: encrypt a password 24 | When I run `eyaml encrypt -o string -p` interactively 25 | And I wait for stderr to contain "Enter password: " 26 | And I type "secretme" 27 | Then the stdout should match /\AENC\[PKCS7,(.*?)\]\z/ 28 | 29 | Scenario: encrypt using STDIN 30 | When I run `eyaml encrypt -o string --stdin` interactively 31 | And I type "encrypt_me" 32 | And I close the stdin stream 33 | Then the output should match /\AENC\[PKCS7,(.*?)\]\z/ 34 | 35 | Scenario: encrypt as string with a label 36 | When I run `eyaml encrypt -o string -s secret_thing -l db-password` 37 | Then the output should match /\Adb-password: ENC\[PKCS7,(.*?)\]\z/ 38 | 39 | Scenario: encrypt as block with a label 40 | When I run `eyaml encrypt -o block -s secret_thing -l db-password` 41 | Then the output should match /db-password: \>\s*ENC\[PKCS7,(.*?)\]$/ 42 | -------------------------------------------------------------------------------- /features/sandbox/puppet/environments/local/test.eyaml: -------------------------------------------------------------------------------- 1 | plaintext_string: good night 2 | encrypted_string: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAQoDt5EgMoJUpsqgcH7OdGJ3d9LzSpaAsgnPuyDHRaHRFHpOmeUk4J4qwHqlNDqD+AMkydaGlNswA/bLdoBwofZGZAUoly/Vkt3ciB4sw0pWlezEUx0dUfWGB95qOJwTqyXhQ4b4fcgCMUbwwCCombN5/LVwjxKHmC9knLNCDkUU5VNDy5Lanh3y8S2XwCJpjArhEcJNfwrf4yz3luQcKD3x91FR3NUYPjJOiTHl9lzxEJm4Mod7ioSj62FcP79qgULWqTKpxMpG4LPwCo/J84jhTkl+fhL/Bs/tmujgpLqBO000kdg4ZmrYrwYqpK6vgcFzVtZJEB3Tq8nXD9hSuAzA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBFKh+qgHj0ajgtgpsFq5NEgBB/nP/DgjeKtd+D2MX6sOTv] 3 | default_encrypted_string: ENC[MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAQoDt5EgMoJUpsqgcH7OdGJ3d9LzSpaAsgnPuyDHRaHRFHpOmeUk4J4qwHqlNDqD+AMkydaGlNswA/bLdoBwofZGZAUoly/Vkt3ciB4sw0pWlezEUx0dUfWGB95qOJwTqyXhQ4b4fcgCMUbwwCCombN5/LVwjxKHmC9knLNCDkUU5VNDy5Lanh3y8S2XwCJpjArhEcJNfwrf4yz3luQcKD3x91FR3NUYPjJOiTHl9lzxEJm4Mod7ioSj62FcP79qgULWqTKpxMpG4LPwCo/J84jhTkl+fhL/Bs/tmujgpLqBO000kdg4ZmrYrwYqpK6vgcFzVtZJEB3Tq8nXD9hSuAzA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBFKh+qgHj0ajgtgpsFq5NEgBB/nP/DgjeKtd+D2MX6sOTv] 4 | encrypted_block: > 5 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 6 | DQYJKoZIhvcNAQEBBQAEggEAYzeWn3MBLhOs4hokxMCWcDd9VuwCylQRUQ0w 7 | KwCObeORw8PJkCDvi5ZIA2YkrvYTT6u3/7KfAiHd0Rg1WLb9et0Mg/Fd3DFF 8 | 7qhqOGHoQt3+4eKzlcikeR0/Lqrq2vTpqZ2Sw1CZ7Dn+Z4ll95p7lp97rb2J 9 | kYTVroLYGWEcsS3JZLL4/l3z0bJbXNKKqJ1aHCAFq+wmWXeb6cDvvyHFg2N/ 10 | vGPFEQjP7AbWhxHxXDbYIGcU073u5NtE40JXL8SH82iHxqRF8s9g6Dh5cmjg 11 | AY2pkBD9e6N78NNx+PAJswsFAV4DOCbXdf2BisyYbM3na35MVfyb6ggDegrE 12 | ebOxxDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWeGlYS5cQoX78L6LK 13 | /mczgCD/pI7usp1XPebnN8CngxHXuUjj5S+6IUpOW6l2JgUeWw==] -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/environments/local/test.eyaml: -------------------------------------------------------------------------------- 1 | plaintext_string: good night 2 | encrypted_string: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAQoDt5EgMoJUpsqgcH7OdGJ3d9LzSpaAsgnPuyDHRaHRFHpOmeUk4J4qwHqlNDqD+AMkydaGlNswA/bLdoBwofZGZAUoly/Vkt3ciB4sw0pWlezEUx0dUfWGB95qOJwTqyXhQ4b4fcgCMUbwwCCombN5/LVwjxKHmC9knLNCDkUU5VNDy5Lanh3y8S2XwCJpjArhEcJNfwrf4yz3luQcKD3x91FR3NUYPjJOiTHl9lzxEJm4Mod7ioSj62FcP79qgULWqTKpxMpG4LPwCo/J84jhTkl+fhL/Bs/tmujgpLqBO000kdg4ZmrYrwYqpK6vgcFzVtZJEB3Tq8nXD9hSuAzA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBFKh+qgHj0ajgtgpsFq5NEgBB/nP/DgjeKtd+D2MX6sOTv] 3 | default_encrypted_string: ENC[MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAQoDt5EgMoJUpsqgcH7OdGJ3d9LzSpaAsgnPuyDHRaHRFHpOmeUk4J4qwHqlNDqD+AMkydaGlNswA/bLdoBwofZGZAUoly/Vkt3ciB4sw0pWlezEUx0dUfWGB95qOJwTqyXhQ4b4fcgCMUbwwCCombN5/LVwjxKHmC9knLNCDkUU5VNDy5Lanh3y8S2XwCJpjArhEcJNfwrf4yz3luQcKD3x91FR3NUYPjJOiTHl9lzxEJm4Mod7ioSj62FcP79qgULWqTKpxMpG4LPwCo/J84jhTkl+fhL/Bs/tmujgpLqBO000kdg4ZmrYrwYqpK6vgcFzVtZJEB3Tq8nXD9hSuAzA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBFKh+qgHj0ajgtgpsFq5NEgBB/nP/DgjeKtd+D2MX6sOTv] 4 | encrypted_block: > 5 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 6 | DQYJKoZIhvcNAQEBBQAEggEAYzeWn3MBLhOs4hokxMCWcDd9VuwCylQRUQ0w 7 | KwCObeORw8PJkCDvi5ZIA2YkrvYTT6u3/7KfAiHd0Rg1WLb9et0Mg/Fd3DFF 8 | 7qhqOGHoQt3+4eKzlcikeR0/Lqrq2vTpqZ2Sw1CZ7Dn+Z4ll95p7lp97rb2J 9 | kYTVroLYGWEcsS3JZLL4/l3z0bJbXNKKqJ1aHCAFq+wmWXeb6cDvvyHFg2N/ 10 | vGPFEQjP7AbWhxHxXDbYIGcU073u5NtE40JXL8SH82iHxqRF8s9g6Dh5cmjg 11 | AY2pkBD9e6N78NNx+PAJswsFAV4DOCbXdf2BisyYbM3na35MVfyb6ggDegrE 12 | ebOxxDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWeGlYS5cQoX78L6LK 13 | /mczgCD/pI7usp1XPebnN8CngxHXuUjj5S+6IUpOW6l2JgUeWw==] -------------------------------------------------------------------------------- /features/sandbox/keys/private_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQq 3 | ErelZUhEi2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK 4 | 1bwD7fCWXrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAE 5 | bBKyRRHHJzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeR 6 | b7FCyOPaLRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvf 7 | Br42sp0KR7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABAoIBAERJKZp/AsatTSP2 8 | dAkqIbu37MiI5rZP97id2/m6NgnCI5oNbOhlMVZB8NiiYrCAUnFlkdwEll7L65AJ 9 | MmmiKHrYby5EPXRnEWHJQrBtkpwXNKwO0gd1JoWIAx0+6DS/HXTp0e2J8jezKhfd 10 | 2UAHOS6bje0SLO9p+/Blk4NmRQjgsYk5nunl463+IkRGU8cna7GIOnvExrT6H3FD 11 | UsL9hL1J/+f4cSmsJoNHtQfKV0OkmQhENpFFwVMunMJPDjGEbaliKqqX05ZnJ1Tu 12 | kDKG+QL1wJbehDNNpiwTGo+Ei8EbP8LYsSkKtMl0zVED636AiuQ259oNDGXo2f+t 13 | FG/1WOECgYEA8OTaHbWgUvtatGBsZ5PIw1VIRG7LZESF0HvlnkZOsCFg2NWrQ99U 14 | sL2gLC0DVi80Yijjk0Kvrp5zU010uU4CuJHzT9ZN/nlPlwTteL6kr8XIh2sTpuE6 15 | nfx4E309d366Hh5xy82AQ+jIeQZTAaPHlFmINrncS48WTfP6lxn5f3ECgYEA33WK 16 | OtImhr4RYRmzcsfybW7PgxxpMSnrex/omZPWEHB14Fa2J6ny3KI9qBP1x6/h6Jfe 17 | SonD4MZ48opiNljfWmgkJMSkqalpa3jDGc8vX1uWhn8NdAuFKAn5zLZC8mZ27VUI 18 | LXLcChjyKPKVTwRnaNe0+rq7MgsqJJ4jo2atgjcCgYEAxPr9+IlKXlC3LQQj4NaR 19 | tliITZ0jqAv4ODD35GKteYzxup2N/GQkxplo3na4YcMb3KB+5y4CppFe0GFn7xcB 20 | VpfSFBizkkD0ehNHdBLAbBMZFNLUMQO/gOyv64/fsVTpMDPI7dRO7DjvpTcsrQyV 21 | 6JMFtWpp30dT/85fvSs6P6ECgYB2Sp2rN7ZHW/SNR3K0T15pSeC2EmMpMHzEyAZ0 22 | zkrilvX/lUeGRbQX0hb7k91nIRdg7owxPy6fHdHG6zTEelV6YWjIwgQ9AD6bMult 23 | Dz2PqEdN2ZJAnRyXLni7Qry73zwTtRDIJmaPPddrj8c0ditb19ypYhJYkopzqfdJ 24 | t8AgDwKBgCyJLYU7pjhfJwOOVJjACa8ndtg0vIr+MGoe99/2hWm7QdX5PrOjDLOr 25 | PeopgafeF1G6chhs4Qqh4LCmDA2NeX+zWcHkjLrKUIXMwb2YPuna7WToyCCeAm8+ 26 | K5Lnssm0P/UJI3CAef/4GXb30ZJYOhsU15/XwY5c8+f6IxRIpPEN 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /features/sandbox/puppet/keys/private_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQq 3 | ErelZUhEi2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK 4 | 1bwD7fCWXrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAE 5 | bBKyRRHHJzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeR 6 | b7FCyOPaLRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvf 7 | Br42sp0KR7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABAoIBAERJKZp/AsatTSP2 8 | dAkqIbu37MiI5rZP97id2/m6NgnCI5oNbOhlMVZB8NiiYrCAUnFlkdwEll7L65AJ 9 | MmmiKHrYby5EPXRnEWHJQrBtkpwXNKwO0gd1JoWIAx0+6DS/HXTp0e2J8jezKhfd 10 | 2UAHOS6bje0SLO9p+/Blk4NmRQjgsYk5nunl463+IkRGU8cna7GIOnvExrT6H3FD 11 | UsL9hL1J/+f4cSmsJoNHtQfKV0OkmQhENpFFwVMunMJPDjGEbaliKqqX05ZnJ1Tu 12 | kDKG+QL1wJbehDNNpiwTGo+Ei8EbP8LYsSkKtMl0zVED636AiuQ259oNDGXo2f+t 13 | FG/1WOECgYEA8OTaHbWgUvtatGBsZ5PIw1VIRG7LZESF0HvlnkZOsCFg2NWrQ99U 14 | sL2gLC0DVi80Yijjk0Kvrp5zU010uU4CuJHzT9ZN/nlPlwTteL6kr8XIh2sTpuE6 15 | nfx4E309d366Hh5xy82AQ+jIeQZTAaPHlFmINrncS48WTfP6lxn5f3ECgYEA33WK 16 | OtImhr4RYRmzcsfybW7PgxxpMSnrex/omZPWEHB14Fa2J6ny3KI9qBP1x6/h6Jfe 17 | SonD4MZ48opiNljfWmgkJMSkqalpa3jDGc8vX1uWhn8NdAuFKAn5zLZC8mZ27VUI 18 | LXLcChjyKPKVTwRnaNe0+rq7MgsqJJ4jo2atgjcCgYEAxPr9+IlKXlC3LQQj4NaR 19 | tliITZ0jqAv4ODD35GKteYzxup2N/GQkxplo3na4YcMb3KB+5y4CppFe0GFn7xcB 20 | VpfSFBizkkD0ehNHdBLAbBMZFNLUMQO/gOyv64/fsVTpMDPI7dRO7DjvpTcsrQyV 21 | 6JMFtWpp30dT/85fvSs6P6ECgYB2Sp2rN7ZHW/SNR3K0T15pSeC2EmMpMHzEyAZ0 22 | zkrilvX/lUeGRbQX0hb7k91nIRdg7owxPy6fHdHG6zTEelV6YWjIwgQ9AD6bMult 23 | Dz2PqEdN2ZJAnRyXLni7Qry73zwTtRDIJmaPPddrj8c0ditb19ypYhJYkopzqfdJ 24 | t8AgDwKBgCyJLYU7pjhfJwOOVJjACa8ndtg0vIr+MGoe99/2hWm7QdX5PrOjDLOr 25 | PeopgafeF1G6chhs4Qqh4LCmDA2NeX+zWcHkjLrKUIXMwb2YPuna7WToyCCeAm8+ 26 | K5Lnssm0P/UJI3CAef/4GXb30ZJYOhsU15/XwY5c8+f6IxRIpPEN 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /features/sandbox/puppet-envvar/keys/private_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQq 3 | ErelZUhEi2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK 4 | 1bwD7fCWXrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAE 5 | bBKyRRHHJzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeR 6 | b7FCyOPaLRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvf 7 | Br42sp0KR7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABAoIBAERJKZp/AsatTSP2 8 | dAkqIbu37MiI5rZP97id2/m6NgnCI5oNbOhlMVZB8NiiYrCAUnFlkdwEll7L65AJ 9 | MmmiKHrYby5EPXRnEWHJQrBtkpwXNKwO0gd1JoWIAx0+6DS/HXTp0e2J8jezKhfd 10 | 2UAHOS6bje0SLO9p+/Blk4NmRQjgsYk5nunl463+IkRGU8cna7GIOnvExrT6H3FD 11 | UsL9hL1J/+f4cSmsJoNHtQfKV0OkmQhENpFFwVMunMJPDjGEbaliKqqX05ZnJ1Tu 12 | kDKG+QL1wJbehDNNpiwTGo+Ei8EbP8LYsSkKtMl0zVED636AiuQ259oNDGXo2f+t 13 | FG/1WOECgYEA8OTaHbWgUvtatGBsZ5PIw1VIRG7LZESF0HvlnkZOsCFg2NWrQ99U 14 | sL2gLC0DVi80Yijjk0Kvrp5zU010uU4CuJHzT9ZN/nlPlwTteL6kr8XIh2sTpuE6 15 | nfx4E309d366Hh5xy82AQ+jIeQZTAaPHlFmINrncS48WTfP6lxn5f3ECgYEA33WK 16 | OtImhr4RYRmzcsfybW7PgxxpMSnrex/omZPWEHB14Fa2J6ny3KI9qBP1x6/h6Jfe 17 | SonD4MZ48opiNljfWmgkJMSkqalpa3jDGc8vX1uWhn8NdAuFKAn5zLZC8mZ27VUI 18 | LXLcChjyKPKVTwRnaNe0+rq7MgsqJJ4jo2atgjcCgYEAxPr9+IlKXlC3LQQj4NaR 19 | tliITZ0jqAv4ODD35GKteYzxup2N/GQkxplo3na4YcMb3KB+5y4CppFe0GFn7xcB 20 | VpfSFBizkkD0ehNHdBLAbBMZFNLUMQO/gOyv64/fsVTpMDPI7dRO7DjvpTcsrQyV 21 | 6JMFtWpp30dT/85fvSs6P6ECgYB2Sp2rN7ZHW/SNR3K0T15pSeC2EmMpMHzEyAZ0 22 | zkrilvX/lUeGRbQX0hb7k91nIRdg7owxPy6fHdHG6zTEelV6YWjIwgQ9AD6bMult 23 | Dz2PqEdN2ZJAnRyXLni7Qry73zwTtRDIJmaPPddrj8c0ditb19ypYhJYkopzqfdJ 24 | t8AgDwKBgCyJLYU7pjhfJwOOVJjACa8ndtg0vIr+MGoe99/2hWm7QdX5PrOjDLOr 25 | PeopgafeF1G6chhs4Qqh4LCmDA2NeX+zWcHkjLrKUIXMwb2YPuna7WToyCCeAm8+ 26 | K5Lnssm0P/UJI3CAef/4GXb30ZJYOhsU15/XwY5c8+f6IxRIpPEN 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/keys/private_key.pkcs7.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0kX0qkf4FOHXuFqlVmbgo+UDrJdneH8XnmWM+stAs0XpzEQq 3 | ErelZUhEi2x5z16RAsEw3cjd2BG78WHz1lcu3NP25BM990YQDl6e9pvmeMNA+wCK 4 | 1bwD7fCWXrAKb7xIXPJVd0ivP82ZPe71CmkFtiFr14mqNWrEpP/d2eQ4PAaCimAE 5 | bBKyRRHHJzU6X+hOnq8GRZBwKh6Ljl31JYE2DG3KQ+ydM+r411jY2sjSykOovbeR 6 | b7FCyOPaLRgyNjiDIJSTRlWLVJbYj6KDSoUD2+R95Owb5Z1OUL9Yn/BRIv5RyDvf 7 | Br42sp0KR7t3pWGiHEuRYZkGDU5jKB/8bK7DRwIDAQABAoIBAERJKZp/AsatTSP2 8 | dAkqIbu37MiI5rZP97id2/m6NgnCI5oNbOhlMVZB8NiiYrCAUnFlkdwEll7L65AJ 9 | MmmiKHrYby5EPXRnEWHJQrBtkpwXNKwO0gd1JoWIAx0+6DS/HXTp0e2J8jezKhfd 10 | 2UAHOS6bje0SLO9p+/Blk4NmRQjgsYk5nunl463+IkRGU8cna7GIOnvExrT6H3FD 11 | UsL9hL1J/+f4cSmsJoNHtQfKV0OkmQhENpFFwVMunMJPDjGEbaliKqqX05ZnJ1Tu 12 | kDKG+QL1wJbehDNNpiwTGo+Ei8EbP8LYsSkKtMl0zVED636AiuQ259oNDGXo2f+t 13 | FG/1WOECgYEA8OTaHbWgUvtatGBsZ5PIw1VIRG7LZESF0HvlnkZOsCFg2NWrQ99U 14 | sL2gLC0DVi80Yijjk0Kvrp5zU010uU4CuJHzT9ZN/nlPlwTteL6kr8XIh2sTpuE6 15 | nfx4E309d366Hh5xy82AQ+jIeQZTAaPHlFmINrncS48WTfP6lxn5f3ECgYEA33WK 16 | OtImhr4RYRmzcsfybW7PgxxpMSnrex/omZPWEHB14Fa2J6ny3KI9qBP1x6/h6Jfe 17 | SonD4MZ48opiNljfWmgkJMSkqalpa3jDGc8vX1uWhn8NdAuFKAn5zLZC8mZ27VUI 18 | LXLcChjyKPKVTwRnaNe0+rq7MgsqJJ4jo2atgjcCgYEAxPr9+IlKXlC3LQQj4NaR 19 | tliITZ0jqAv4ODD35GKteYzxup2N/GQkxplo3na4YcMb3KB+5y4CppFe0GFn7xcB 20 | VpfSFBizkkD0ehNHdBLAbBMZFNLUMQO/gOyv64/fsVTpMDPI7dRO7DjvpTcsrQyV 21 | 6JMFtWpp30dT/85fvSs6P6ECgYB2Sp2rN7ZHW/SNR3K0T15pSeC2EmMpMHzEyAZ0 22 | zkrilvX/lUeGRbQX0hb7k91nIRdg7owxPy6fHdHG6zTEelV6YWjIwgQ9AD6bMult 23 | Dz2PqEdN2ZJAnRyXLni7Qry73zwTtRDIJmaPPddrj8c0ditb19ypYhJYkopzqfdJ 24 | t8AgDwKBgCyJLYU7pjhfJwOOVJjACa8ndtg0vIr+MGoe99/2hWm7QdX5PrOjDLOr 25 | PeopgafeF1G6chhs4Qqh4LCmDA2NeX+zWcHkjLrKUIXMwb2YPuna7WToyCCeAm8+ 26 | K5Lnssm0P/UJI3CAef/4GXb30ZJYOhsU15/XwY5c8+f6IxRIpPEN 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /features/keys.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml key generation 2 | 3 | In order to encrypt data with various encryption methods 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to generate keys and certs 6 | 7 | Scenario: create some pkcs7 keys 8 | When I run `eyaml createkeys --pkcs7-public-key keys/new_public_key.pem --pkcs7-private-key keys/new_private_key.pem` 9 | Then the output should match /Keys created OK/ 10 | 11 | Scenario: decline to overwrite some pkcs7 keys 12 | When I run `touch keys/new_private_key.pem` 13 | And I run `eyaml createkeys --pkcs7-public-key keys/new_public_key.pem --pkcs7-private-key keys/new_private_key.pem` interactively 14 | And I wait for stderr to contain "Are you sure you want to overwrite \"keys/new_private_key.pem\"? (y/N): " 15 | And I type "" 16 | Then the output should match /User aborted/ 17 | 18 | Scenario: overwrite some pkcs7 keys 19 | When I run `touch keys/new_public_key.pem keys/new_private_key.pem` 20 | And I run `eyaml createkeys --pkcs7-public-key keys/new_public_key.pem --pkcs7-private-key keys/new_private_key.pem` interactively 21 | And I wait for stderr to contain "Are you sure you want to overwrite \"keys/new_private_key.pem\"? (y/N): " 22 | And I type "y" 23 | And I wait for stderr to contain "Are you sure you want to overwrite \"keys/new_public_key.pem\"? (y/N): " 24 | And I type "y" 25 | Then the output should match /Keys created OK/ 26 | 27 | Scenario: create missing pkcs7 keys dir 28 | When I run `eyaml createkeys --pkcs7-public-key keys/missing/new_public_key.pem --pkcs7-private-key keys/missing/new_private_key.pem` 29 | Then the output should contain "Created key directory: keys/missing" 30 | 31 | Scenario: create some plaintext keys 32 | When I run `eyaml createkeys -n plaintext` 33 | Then the output should match /success/ 34 | -------------------------------------------------------------------------------- /features/sandbox/test_multiline_input.eyaml: -------------------------------------------------------------------------------- 1 | key: > 2 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 3 | DQYJKoZIhvcNAQEBBQAEggEAysrJF6961u/GUpn37ZUDTrtDFZO4qBHY4xYs 4 | ttOI2X6Gtd1SYPSYWJZfCJjixQBcPvVdRfhkVqQlX547vPLsumbuUCsUHju8 5 | vG4E8ADMCBYeX+FMgDZQi27ZjfmZs01SBuPw6et8R+5A5Zkf2hjS6gbzZ4PJ 6 | 62kYCB08T/fFG3QjlOneNY8AQFiRnftvvvS1zFzSWQ2jd/CTpl+fC+DCQ6Jb 7 | GvaryayzrAVbtYM6H7MLauHDYA3mXsmPEN5Chm9LU6zr/O5wh3mR0Q7bNcPm 8 | /LBUlWoz517OTiplOwN+ZlOIJjCrP5F/huMoDhE8782LL0kEdw8TqE/Zubag 9 | uYLxzzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAOS2HkmYEMFXBfuctI 10 | rzCMgCCePBbWTx8QNihgzQ/fHF0fy9co2NRUQ6KBLaCvBVP0vQ==] 11 | notthekey: > 12 | ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAw 13 | DQYJKoZIhvcNAQEBBQAEggEAhgfdYg1cjvrJcF8xFyWDo7znl+6707QUhp1P 14 | IJJirXvVSjP47BSnVvt/x9578DAE88EHoCy9BZmkpYYqqEyBRXAZa+yYIQin 15 | KovYnnIwYBCBTV+QnhvzUoqGunjY+OIPZAjfb1Pmn2T8B7/pWPO9B2IusIb4 16 | 3yVc097ZLcQoYaZ5+g4NWn87W3mMuOA7PIjQPIu1P2pLUQv0XSqvZx+O0nGt 17 | AqAjfboeTcKf6JFlU/Eabf7QfqsaAqGUUR6JJFn+jV79XoYuksNFyqW1Xui8 18 | Rdn2RW1m+c/ublQZggAzL8kQaCxKpmEvtGM0rtYtZyC8V/vdTT/V0vWOPn7m 19 | GLe7PjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAbt3ZRq+iH+b7nAl8h 20 | god+gBB4nqLQXUM1ruGie+tyoboh] 21 | theotherkey: > 22 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 23 | DQYJKoZIhvcNAQEBBQAEggEAe2w+eVbWFG369tP0N2tCkRCD0xOCxB2Dhk4Z 24 | EiDlq7PPujB+CP7eG41tq1fSxZ+bVnGUbkdwvP7tfsF7/zEZKqcJvMZL50TY 25 | /iNe7D95O/BQJOsF1LhGT2x7wGpy6rv2HCRtkVQlQlzw81kZYNKgKjpPsWB0 26 | WIOwdFqgAMbVb6SqnsVcybdmQZZf0rjX7tapKJg7OUdAgRKdANoSZOveBWDS 27 | PmiFO2cHLsTpFLxS+BAUdkzxW8Zuhey3IBCX+jfFlonH6wE7+LZuHP9WOrHa 28 | xV4P1gfP+uZPqCHjvQ8NK3eHEPCCt9m0ie7U/Ljpypi3j4dzvqkvXD3WcOnl 29 | EgVC4zBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBSb2+BlVgtdLaScpbp 30 | zdqDgCCCd8axwP2jIYgnkIJZoW89lQqgL7K77GpSnYm6XPc6HQ==] 31 | -------------------------------------------------------------------------------- /features/sandbox/test_multiline_win_input.eyaml: -------------------------------------------------------------------------------- 1 | key: > 2 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 3 | DQYJKoZIhvcNAQEBBQAEggEAEt+JnMeWYd3in9MZLqds3we4Lb/x6lF1Bq6w 4 | 6UMFT4LWJMp3cpSGvfBYgr2B4+xQa6ppWRDcZvC7buGaxeSTmjKXmHvrKpaL 5 | NQkSwCyjU9dNGiY4ZI24VpUWO9ylkcD0RgI6N8j0PhGoZNcNz1hqXN2AbT1X 6 | 7LPawqkZ0LRP0fLUq+npZCmrOY9EI6WMHy6FfF/n1hPClBFDNdoJtY1rKyDq 7 | 746dtjD2g73kuxCSUzJZCGOpiovLSv/EnRXUuFf7E6PV7dEgaCA9E6bn9kWB 8 | BYEGuWTVefq3jNljw053eDJYH0ixMnWTcfnjIPLvXiJG+FuPO2ca5hvNGbXr 9 | O9fddzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCD63Z26kV2ObTSy+zs 10 | cmJagCDnvPII+ym0WCZhndbZPCIirnh3QARUOf+dukuyp8elVw==] 11 | notthekey: > 12 | ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAw 13 | DQYJKoZIhvcNAQEBBQAEggEAT2UcQ/rmNsMPfhrP3+xajRwroJkGY5DBLL6a 14 | C3uZlE9eor7sdF1VffKTL7V2qm0dXT30KoepSlKSvOrNVl0+hiAJRYUlYIVX 15 | /I44hteDLA47MGgW+RZSivQU1+wd+cIU3ATGmB8qzb8S97wuDMC+3DfCgoja 16 | gCdphhw4D8+PiIz+/1fY2nnCvg8Gm3dbJhTv1CnlO/E6BhIwElV+XDyg1qzH 17 | H0EEc8uqpGBx+4FJ3TtxzNI96bx1IermhGPHuPPjPh7QD7CLgDx2bwehKK8O 18 | AM7EzY03w+k+pgOuYAmC0btdDIY7NVULLXmUNPwM9AG/gIvOrsna8Bhf2Fee 19 | UwIAYjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBBzYolRTnNj1WFqsV9 20 | NWHYgBC98uIpgR+Jn+JpCLp9+f0a] 21 | theotherkey: > 22 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 23 | DQYJKoZIhvcNAQEBBQAEggEAyJp4XKuqIpye9isff+KPhBtWWnM9yTYHWVo5 24 | dL82pI3qq3ov2k4ylg3e+HHfzp+oVlx5EETj0rB5VE/FG+GUbFS6Um8Kawf1 25 | afl/3VaYmR6Z5kaQIopKENL7o5TbFOGkhW97ENMsu6iWlUwfJHF92VYqC5gk 26 | j0U+Wl6beNSmbKzBRYYywb0X4fu3dVzHpkxSnXJBPiDkhiyp7pXLj8McnYGy 27 | 5QcHLQQ/6fJclIunOa/2mljRVKm725O43yVCpI3Pk33GAodp5KZ0o+fWixsS 28 | zzpL1NyGjAGjq0Bg+FBSgNkTzlnqJKx4N8vxSmdSpLMcA7Y9iTx1/mO9AUzp 29 | J/GPnTBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBB7UwSBOE2Q/LDDOqm1 30 | GyjVgCDqHDJ6StbRWHIFhTmtUnsVq/DIh/SMiqh1WNEgXz9CPA==] 31 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/recrypt.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/subcommand' 2 | require 'hiera/backend/eyaml/options' 3 | require 'hiera/backend/eyaml/parser/parser' 4 | 5 | class Hiera 6 | module Backend 7 | module Eyaml 8 | module Subcommands 9 | class Recrypt < Subcommand 10 | def self.options 11 | [ 12 | { name: :change_encryption, 13 | description: 'Specify the new encryption method that should be used for the file', 14 | short: 'd', 15 | default: 'pkcs7', }, 16 | ] 17 | end 18 | 19 | def self.description 20 | 'recrypt an eyaml file' 21 | end 22 | 23 | def self.helptext 24 | 'Usage: eyaml recrypt [options] ' 25 | end 26 | 27 | def self.validate(options) 28 | Optimist.die 'You must specify an eyaml file' if ARGV.empty? 29 | options[:source] = :eyaml 30 | options[:eyaml] = ARGV.shift 31 | options[:input_data] = File.read options[:eyaml] 32 | @change_encryption = options[:change_encryption] 33 | options 34 | end 35 | 36 | def self.execute 37 | encrypted_parser = Parser::ParserFactory.encrypted_parser 38 | tokens = encrypted_parser.parse Eyaml::Options[:input_data] 39 | decrypted_input = tokens.each_with_index.to_a.map { |(t, index)| t.to_decrypted index: index }.join 40 | 41 | decrypted_parser = Parser::ParserFactory.decrypted_parser 42 | edited_tokens = decrypted_parser.parse(decrypted_input) 43 | 44 | encrypted_output = edited_tokens.map { |t| t.to_encrypted({ change_encryption: @change_encryption }) }.join 45 | 46 | filename = Eyaml::Options[:eyaml] 47 | File.write("#{filename}", encrypted_output) 48 | 49 | nil 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /features/recrypt.feature: -------------------------------------------------------------------------------- 1 | Feature: Recrypt 2 | In order to handle require changes in crypt 3 | I want to be able to re-encrypt all keys in file 4 | 5 | Scenario: Recrypt encrypted yaml 6 | Given I recrypt a file 7 | And I configure the keypair 8 | And I load a file called test_input.yaml 9 | And I recrypt it twice 10 | Then I should have 35 tokens 11 | Then the recrypted tokens should match 12 | Then the recrypted decrypted content should match 13 | Then the recrypted contents should differ 14 | Then the tokens at 1 should match 15 | Then the tokens at 5 should match 16 | 17 | Scenario: Recrypt encrypted yaml with keypair as envvars 18 | Given I recrypt a file 19 | And I configure the keypair using envvars 20 | And I load the keypair into envvars 21 | And I load a file called test_input.yaml 22 | And I recrypt it twice 23 | Then I should have 35 tokens 24 | Then the recrypted tokens should match 25 | Then the recrypted decrypted content should match 26 | Then the recrypted contents should differ 27 | Then the tokens at 1 should match 28 | Then the tokens at 5 should match 29 | 30 | Scenario: Recrypt encrypted yaml using the eyaml tool 31 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 32 | And I run `eyaml recrypt test_input.yaml` 33 | When I run `diff -q test_input.yaml test_input.eyaml` 34 | Then the exit status should be 1 35 | And I run `eyaml decrypt -e test_input.eyaml` 36 | Then the output should match /encrypted_string: DEC::PKCS7\[planet of the apes\]\!/ 37 | And I run `eyaml recrypt -d plaintext test_input.eyaml` 38 | Then the exit status should be 0 39 | And I run `eyaml decrypt -e test_input.eyaml` 40 | Then the output should match /encrypted_string: DEC::PLAINTEXT\[planet of the apes\]\!/ 41 | And I run `eyaml recrypt -d pkcs7 test_input.eyaml` 42 | Then the exit status should be 0 43 | And I run `eyaml decrypt -e test_input.eyaml` 44 | Then the output should match /encrypted_string: DEC::PKCS7\[planet of the apes\]\!/ -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/plugins.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | class Hiera 4 | module Backend 5 | module Eyaml 6 | class Plugins 7 | @@plugins = [] 8 | @@commands = [] 9 | @@options = [] 10 | 11 | def self.register_options(args) 12 | options = args[:options] 13 | plugin = args[:plugin] 14 | options.each do |name, option_hash| 15 | new_option = { name: "#{plugin}_#{name}" } 16 | new_option.merge! option_hash 17 | @@options << new_option 18 | end 19 | end 20 | 21 | def self.options 22 | @@options 23 | end 24 | 25 | def self.find 26 | gem_version = Gem::Version.new(Gem::VERSION) 27 | this_version = Gem::Version.create(Hiera::Backend::Eyaml::VERSION) 28 | index = (gem_version >= Gem::Version.new('1.8.0')) ? Gem::Specification : Gem.source_index 29 | 30 | [index].flatten.each do |source| 31 | specs = (gem_version >= Gem::Version.new('1.6.0')) ? source.latest_specs(true) : source.latest_specs 32 | 33 | specs.each do |spec| 34 | spec = spec.to_spec if spec.respond_to?(:to_spec) 35 | next if @@plugins.include? spec 36 | 37 | dependency = spec.dependencies.find { |d| d.name == 'hiera-eyaml' } 38 | next if dependency && !dependency.requirement.satisfied_by?(this_version) 39 | 40 | file = if gem_version >= Gem::Version.new('1.8.0') 41 | spec.matches_for_glob('**/eyaml_init.rb').first 42 | else 43 | Gem.searcher.matching_files(spec, '**/eyaml_init.rb').first 44 | end 45 | 46 | next unless file 47 | 48 | @@plugins << spec 49 | load file 50 | end 51 | end 52 | 53 | @@plugins 54 | end 55 | 56 | def self.plugins 57 | @@plugins 58 | end 59 | 60 | def self.commands 61 | @@commands 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/environments/local/test.eyaml: -------------------------------------------------------------------------------- 1 | --- 2 | "test::run": 3 | plaintext_string: good night 4 | encrypted_string: ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAFAf9nrUhsOtum+Ky75uNQUYYYX/qzD1Vkro6CKwkhgZb1NcpOmbmjOupbzRzV1Wsi2jzbRgG90nYm0prhw4mEy4UVIu1qbxt0QJkIy99uRs0KmuRO21cWjuU1kPeQCu1sZt8Nq2wzKZxZbLviktOxmIEdHPMeD3QEOUDSShHB+D19AJ0+ttFCpsjfIItiaRe1q0t3dgQj6UCry6x1RsdCAadH1wauhuol82i2PLg2NUgquxPvVEbesUJjbnecNX+ffZGPTYTo/CKl9jH6qpYPD5x/V6aNCJW/Y5kdzph1B3XcBFcLPGno2xymtjdSf9eG9JhtFrn7uNYMS/YZEhnNjBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDTY863lO230iJQ77gInAFHgCApAGviEbByW2/mI0t2i3m3w1ERo0A2Um6QbxKF6jGVXw==] 5 | encrypted_array: 6 | - ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAtXNJhaSgC4n+o3JlBzmiG39sxlki5LuCmpLuCZrTttroeQnfsl+RrFMZgWr22V5oFwl+LdPZgmQG/XviLV1Jh5N6qOao7Km1pamaOD3/njCWgNuw0BJQ+QEqXpf3zyODOAID8y1YZzSfwUvUWvZC/Tl6wTqU7vW1Wi4c2TscokXoFFSr9N0el52yNqHWSnQl5LsTRoloStQsNLbYJHWl/Nqey6vfHTt7Drk/D45e2FP7gzqiJvj5+YxAltkILlBp4tBVGgurRoDGfvacCS1gkp1AyxLg00WROW5c95fxZ+QUc/x9TrbPdKNpuYdv6mqUgWGE4V9YwEyXosEJijHqajA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBC6w9cAUqBFPSTprViuPndSgBC9SZ8AVbdx5Gq717/gvdS4] 7 | encrypted_hash: 8 | here: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAeIvUzdnZZb1yXAFylGbQx+O7Ugi2VnM0ZmT/SxH9uVnBrSPwm9zIkEL3q+O48ujDMkf9hRAxjr1IOMwgu/aKGOYb+fpOfkD68KTwiBNTjn4on1QqrjlKMdjDjdFTXzswZzGxDaZP18kBcPeIYfUMnb2lLpHPj5ZNBfvVscTfmKcg62OmigQKN4nIzVIq/eZPh4dWYYocLwVDo5EHXUGGl6aR232l8HhygbUBWcLkS89C7CRF5TzAvqjfVKE5WsPM3SAZXZNbzY8qRl3C4rWuPU12u86vkKhB0NkM2I1bNlIGZlGAXBrE+QnrhG4LB77CNu4Y2UP1YQoKUDA/IcUG0TA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDNDDRNoCswfwrPHP3bvUzYgBD+OVAhftY4De+8t9D9Xbei] 9 | encrypted_block: > 10 | ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQAw 11 | DQYJKoZIhvcNAQEBBQAEggEAqXiipYO08odLjqXxdNRSwTxWWBXkk7QF/0qR 12 | J5fieLkQcWtKkEt0+mBK7d24oimJIwrdVss9U2jLvZ3q3Q3zUnarDkSf2lij 13 | PNmwnTqjW/6job8JYiwVy6jKWcV6xI0iI1xCkyg6uZNEqU3cOAftRKF4BBjg 14 | 5nEfWrl8eeCATvMrHM1/0J6cXZU42RYr01onfG+f8gESjUfFaV8fa50dt8yc 15 | JBTyTC7k8q5uh1EbM+qMLMZ4kZZ2RltNjIqB1Mi5MjKpTKaHtLjA2Y0XVyd4 16 | ad1lA9YlMYtKr2nRVDQvk1LP3QdhhUfGCS3cbCQrcDx43NIuBb4qKPLpBgwJ 17 | Q8Bc9TBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAjPN12g//PicYv5eUR 18 | LsfZgDDbvGpWd8bgiLGyc02UBQ1XUlkz0GFeoE429E/ZYKbYG8T9FryjzJk5 19 | aDhtoIB3sx0=] 20 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/utils.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'fileutils' 3 | require 'hiera/backend/eyaml/logginghelper' 4 | 5 | class Hiera 6 | module Backend 7 | module Eyaml 8 | class Utils 9 | def self.camelcase(string) 10 | return string if string !~ /_/ && string =~ /[A-Z]+.*/ 11 | 12 | string.split('_').map { |e| e.capitalize }.join 13 | end 14 | 15 | def self.snakecase(string) 16 | return string unless /[A-Z]/.match?(string) 17 | 18 | string.split(/(?=[A-Z])/).collect { |x| x.downcase }.join('_') 19 | end 20 | 21 | def self.find_closest_class(args) 22 | parent_class = args[:parent_class] 23 | class_name = args[:class_name] 24 | constants = parent_class.constants 25 | candidates = [] 26 | constants.each do |candidate| 27 | candidates << candidate.to_s if candidate.to_s.downcase == class_name.downcase 28 | end 29 | return unless candidates.count > 0 30 | 31 | parent_class.const_get candidates.first 32 | end 33 | 34 | def self.require_dir(classdir) 35 | num_class_hierarchy_levels = to_s.split('::').count - 1 36 | root_folder = File.dirname(__FILE__) + '/' + Array.new(num_class_hierarchy_levels).fill('..').join('/') 37 | class_folder = root_folder + '/' + classdir 38 | Dir[File.expand_path("#{class_folder}/*.rb")].uniq.each do |file| 39 | LoggingHelper.trace "Requiring file: #{file}" 40 | require file 41 | end 42 | end 43 | 44 | def self.find_all_subclasses_of(args) 45 | parent_class = args[:parent_class] 46 | constants = parent_class.constants 47 | candidates = [] 48 | constants.each do |candidate| 49 | candidates << candidate.to_s.split('::').last if parent_class.const_get(candidate).instance_of?(::Class) 50 | end 51 | candidates 52 | end 53 | 54 | def self.hiera? 55 | 'hiera'.eql? Eyaml::Options[:source] 56 | end 57 | 58 | def self.convert_to_utf_8(string) 59 | orig_encoding = string.encoding 60 | return string if orig_encoding == Encoding::UTF_8 61 | 62 | string.dup.force_encoding(Encoding::UTF_8) 63 | rescue EncodingError 64 | warn "Unable to encode to \"Encoding::UTF_8\" using the original \"#{orig_encoding}\"" 65 | string 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/encryptor.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | require 'hiera/backend/eyaml/encrypthelper' 3 | 4 | class Hiera 5 | module Backend 6 | module Eyaml 7 | class Encryptor 8 | class << self 9 | attr_accessor :options, :tag 10 | end 11 | 12 | def self.find(encryption_scheme = nil) 13 | encryption_scheme = Eyaml.default_encryption_scheme if encryption_scheme.nil? 14 | require "hiera/backend/eyaml/encryptors/#{File.basename encryption_scheme.downcase}" 15 | encryptor_module = Module.const_get(:Hiera).const_get(:Backend).const_get(:Eyaml).const_get(:Encryptors) 16 | encryptor_class = Utils.find_closest_class parent_class: encryptor_module, class_name: encryption_scheme 17 | if encryptor_class.nil? 18 | raise StandardError, 19 | "Could not find hiera-eyaml encryptor: #{encryption_scheme}. Try gem install hiera-eyaml-#{encryption_scheme.downcase} ?" 20 | end 21 | 22 | encryptor_class 23 | end 24 | 25 | def self.encode(binary_string) 26 | Base64.strict_encode64(binary_string) 27 | end 28 | 29 | def self.decode(string) 30 | Base64.decode64(string) 31 | end 32 | 33 | def self.encrypt *_args 34 | raise StandardError, "encrypt() not defined for encryptor plugin: #{self}" 35 | end 36 | 37 | def self.decrypt *_args 38 | raise StandardError, "decrypt() not defined for decryptor plugin: #{self}" 39 | end 40 | 41 | def self.plugin_classname 42 | to_s.split('::').last.downcase 43 | end 44 | 45 | def self.register 46 | Hiera::Backend::Eyaml::Plugins.register_options options: options, plugin: plugin_classname 47 | end 48 | 49 | def self.option(name) 50 | Eyaml::Options["#{plugin_classname}_#{name}"] || options["#{plugin_classname}_#{name}"] 51 | end 52 | 53 | def self.hiera? 54 | Utils.hiera? 55 | end 56 | 57 | def self.format_message(msg) 58 | "[eyaml_#{plugin_classname}]: #{msg}" 59 | end 60 | 61 | def self.trace(msg) 62 | LoggingHelper.trace from: plugin_classname, msg: msg 63 | end 64 | 65 | def self.debug(msg) 66 | LoggingHelper.debug from: plugin_classname, msg: msg 67 | end 68 | 69 | def self.info(msg) 70 | LoggingHelper.info from: plugin_classname, msg: msg 71 | end 72 | 73 | def self.warn(msg) 74 | LoggingHelper.warn from: plugin_classname, msg: msg 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | 4 | on: 5 | pull_request: {} 6 | push: 7 | branches: 8 | - master 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | rubocop: 15 | env: 16 | BUNDLE_WITHOUT: release 17 | runs-on: ubuntu-24.04 18 | steps: 19 | - uses: actions/checkout@v6 20 | - name: Install Ruby ${{ matrix.ruby }} 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: "3.3" 24 | bundler-cache: true 25 | - name: Run Rubocop 26 | run: bundle exec rake rubocop 27 | test: 28 | runs-on: ubuntu-24.04 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | ruby: 33 | - "2.7" 34 | - "3.0" 35 | - "3.1" 36 | - "3.2" 37 | - "3.3" 38 | - jruby-9.4 39 | openvox: 40 | - "~> 8" 41 | - "~> 7" 42 | - "https://github.com/OpenVoxProject/puppet.git#main" 43 | exclude: 44 | - ruby: "3.0" 45 | openvox: "~> 8" 46 | - ruby: "2.7" 47 | openvox: "~> 8" 48 | 49 | - ruby: "3.0" 50 | openvox: "https://github.com/openvoxlabs/puppet.git#main" 51 | - ruby: "2.7" 52 | openvox: "https://github.com/openvoxlabs/puppet.git#main" 53 | 54 | - ruby: "3.0" 55 | openvox: "https://github.com/openvoxproject/puppet.git#main" 56 | - ruby: "2.7" 57 | openvox: "https://github.com/openvoxproject/puppet.git#main" 58 | 59 | env: 60 | OPENVOX_VERSION: ${{ matrix.openvox }} 61 | COVERAGE: ${{ matrix.coverage }} 62 | name: "Ruby ${{ matrix.ruby }} - OpenVox ${{ matrix.openvox }}" 63 | steps: 64 | - name: Enable coverage reporting on Ruby 3.1 65 | if: matrix.openvox == '~> 7.24' && matrix.ruby == '3.1' 66 | run: echo 'COVERAGE=yes' >> $GITHUB_ENV 67 | - uses: actions/checkout@v6 68 | - name: Install Ruby ${{ matrix.ruby }} 69 | uses: ruby/setup-ruby@v1 70 | with: 71 | ruby-version: ${{ matrix.ruby }} 72 | bundler-cache: true 73 | - name: Display Ruby environment 74 | run: bundle env 75 | - name: spec tests 76 | run: bundle exec rake features 77 | - name: Verify gem builds 78 | run: gem build --strict --verbose *.gemspec 79 | 80 | tests: 81 | if: always() 82 | needs: 83 | - rubocop 84 | - test 85 | runs-on: ubuntu-24.04 86 | name: Test suite 87 | steps: 88 | - name: Decide whether the needed jobs succeeded or failed 89 | uses: re-actors/alls-green@release/v1 90 | with: 91 | jobs: ${{ toJSON(needs) }} 92 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/parser/parser.rb: -------------------------------------------------------------------------------- 1 | require 'strscan' 2 | require 'hiera/backend/eyaml/parser/token' 3 | require 'hiera/backend/eyaml/parser/encrypted_tokens' 4 | 5 | class Hiera 6 | module Backend 7 | module Eyaml 8 | module Parser 9 | class ParserFactory 10 | def self.encrypted_parser 11 | enc_string = EncStringTokenType.new 12 | enc_block = EncBlockTokenType.new 13 | Parser.new([enc_string, enc_block]) 14 | end 15 | 16 | def self.decrypted_parser 17 | dec_string = DecStringTokenType.new 18 | dec_block = DecBlockTokenType.new 19 | Parser.new([dec_string, dec_block]) 20 | end 21 | 22 | def self.hiera_backend_parser 23 | enc_hiera = EncHieraTokenType.new 24 | Parser.new([enc_hiera]) 25 | end 26 | end 27 | 28 | class Parser 29 | attr_reader :token_types 30 | 31 | def initialize(token_types) 32 | @token_types = token_types 33 | end 34 | 35 | def parse(text) 36 | parse_scanner(StringScanner.new(text)).reverse 37 | end 38 | 39 | def parse_scanner(s) 40 | if s.eos? 41 | [] 42 | else 43 | # Check if the scanner currently matches a regex 44 | current_match = @token_types.find do |token_type| 45 | s.match?(token_type.regex) 46 | end 47 | 48 | token = 49 | if current_match.nil? 50 | # No regex matches here. Find the earliest match. 51 | next_match_indexes = @token_types.map do |token_type| 52 | next_match = s.check_until(token_type.regex) 53 | if next_match.nil? 54 | nil 55 | else 56 | next_match.length - s.matched.length 57 | end 58 | end.reject { |i| i.nil? } 59 | non_match_size = 60 | if next_match_indexes.length == 0 61 | s.rest_size 62 | else 63 | next_match_indexes.min 64 | end 65 | non_match = s.peek(non_match_size) 66 | # advance scanner 67 | s.pos = s.pos + non_match_size 68 | NonMatchToken.new(non_match) 69 | else 70 | # A regex matches so create a token and do a recursive call with the advanced scanner 71 | current_match.create_token s.scan(current_match.regex) 72 | end 73 | 74 | parse_scanner(s) << token 75 | end 76 | end 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/logginghelper.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'fileutils' 3 | 4 | class Hiera 5 | module Backend 6 | module Eyaml 7 | class LoggingHelper 8 | def self.structure_message(messageinfo) 9 | message = { from: 'hiera-eyaml-core' } 10 | case messageinfo.class.to_s 11 | when 'Hash' 12 | message.merge!(messageinfo) 13 | else 14 | message.merge!({ msg: messageinfo.to_s }) 15 | end 16 | message[:prefix] = "[#{message[:from]}]" 17 | message[:spacer] = " #{' ' * message[:from].length} " 18 | formatted_output = message[:msg].split("\n").each_with_index.map do |line, index| 19 | if index == 0 20 | "#{message[:prefix]} #{line}" 21 | else 22 | "#{message[:spacer]} #{line}" 23 | end 24 | end 25 | formatted_output.join "\n" 26 | end 27 | 28 | def self.warn(messageinfo) 29 | print_message({ message: structure_message(messageinfo), hiera_loglevel: :warn, cli_color: :red }) 30 | end 31 | 32 | def self.info(messageinfo) 33 | print_message({ message: structure_message(messageinfo), hiera_loglevel: :debug, cli_color: :white, threshold: 0 }) 34 | end 35 | 36 | def self.debug(messageinfo) 37 | print_message({ message: structure_message(messageinfo), hiera_loglevel: :debug, cli_color: :green, threshold: 1 }) 38 | end 39 | 40 | def self.trace(messageinfo) 41 | print_message({ message: structure_message(messageinfo), hiera_loglevel: :debug, cli_color: :blue, threshold: 2 }) 42 | end 43 | 44 | def self.print_message(args) 45 | message = args[:message] ||= '' 46 | hiera_loglevel = args[:hiera_loglevel] ||= :debug 47 | cli_color = args[:cli_color] ||= :blue 48 | threshold = args[:threshold] 49 | 50 | if hiera? 51 | Hiera.send(hiera_loglevel, message) if threshold.nil? or Eyaml.verbosity_level > threshold 52 | elsif threshold.nil? or Eyaml.verbosity_level > threshold 53 | STDERR.puts self.colorize(message, cli_color) 54 | end 55 | end 56 | 57 | def self.colorize(message, color) 58 | suffix = "\e[0m" 59 | prefix = case color 60 | when :red 61 | "\e[31m" 62 | when :green 63 | "\e[32m" 64 | when :blue 65 | "\e[34m" 66 | else # :white 67 | "\e[0m" 68 | end 69 | "#{prefix}#{message}#{suffix}" 70 | end 71 | 72 | def self.hiera? 73 | 'hiera'.eql? Eyaml::Options[:source] 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /features/sandbox/puppet-hiera-merge/environments/local/city/test.eyaml: -------------------------------------------------------------------------------- 1 | --- 2 | "test::run": 3 | plaintext_string: rise and shine 4 | encrypted_string: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAg93VPUWOAsfHPhmXtcukXiL/ns3KrdyZyVb3uk507KiGQMtdMCutWhOHLPoFgPvGj7Ud6el6YOwPmxKZIxVsfbHpeqDV3NX12EPOFcVgP/uhy0iD6/BcJP0cks7iF7s+HEZ7OkqGsHU7xfnOmpuECHxTVYN1oUpA8OilUMd+d0llztAEvIa3OxWR5Pt86HhWunym1VDZ95ANDby8wvXcYXM71/YAyioWdrCvf3md527lnugYeYzNP0tELHGzBboulOSrPZB0tNvayR18sdHkKGVci0NBoel3dyQB7JvA6EC9VBn1TwRsd7OZVBtvq+4U5PKT1nNwLV+Kt7t7ajQjkzA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDzCfhlQP40awETXlZVWeCigBBx1ayYErwlVku8Jo1lgyW0] 5 | encrypted_array: 6 | - ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAAPMM4yuMGYZKMtS/p9rdMQME//3MatRg1p1FeZZjey2gu3NFgd1AA+lJzonrdxxq881PfScF7jPe50kSkEG5JrNg5mlhunmRHs27IkJrpHgyi5/LTE97xDHKF0qjGDe3GhQUH0fHgCV3QQ/RvJfO1wOSaJjwKu3+h8+sGchOeXudxQsMml5AJcxge64KWQB8ov4KDBBQoKLkHnpfiqPKcNg0PuixvBbYFb6k92Tunf7V+PbPuX84yP3Rdchvu+hCZtMAw/Yi2QXwDChFnQ2EIH5QJX6w56Oey1DoVW3EjqiqVVVGL/K8znS2t19DK0bC0xO0SixDCoC/YVU+7He6/DBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCpxTAcOGzNogIA4IZmmZs/gCAWSQ3bUqtA8c3i7Y6VN4Nv90om4f0BLK0ZO9YlysbXOw==] 7 | encrypted_hash: 8 | here: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAHTemKWAO3cZQMtlKdrgSD2VmKW5k6doex7c6DEoUG6jpjpGe6JXqgc/OAPWXuuL6hEzIIWIE34/Ym2Q1kr7IaGgMPrJsxUdTT7g5SUFfDMd7h11eRtoNRg8cybTqi6FB0AsVsPPicoIjS17FmAdrVvfxQYujX+QwGfsXwR8CgJtX4TocZTr6gxWdm//Xhvrh7tajkDdRGeiAgZQave0Nt/qbYn1kdzIT4ieT8lA9S3l65FEgw60l/m8UmHmm1XKhr1L11z52FbuFrezxN9NsXTLBBd/qP0iEWMjxJik3GkNn+LLzvplWYCGz0atsPx7lqVJi3L60xKgmolR8LCP01zA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBC8QS/Wd8PNmpZioua2KD1FgBBiPH3vGtvbbyufM9E6Ev/c] 9 | see: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAqKzMLMzzzaHcdINA+LmCwFFVFlaABA1OOr1lKfrazvcvUszmJL1FTJc3w1FjlQbQwXPzqaVRTWNO7nQJ2kge0zxIikxaQwtq1YUsNHZavw8nuG0U3OFvLPdWGJq6XUaZ4Tnb13RZKUxCVHRZpdlDTQ/avS3kUCnwrLC+dcwTxmsdC0PwKxM9WptcD115sx1QPJu3G9lK8aiAbQPI4i69708i9E/nEJd3Z0iceC7eGo/cjBPQik0rpE8S5UxxT5+IE7TzBz0U7nuGAHBYbQYkECagblM1aFuWYKktga89CAA/9sULHZD/QdLjZq+8JAIXQs2YWncGYprHMeQtcpCo7TA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDiHSaTnouPKVgFa31kATVegBB0X0qTRPNRIydGT7XqMaGG] 10 | encrypted_block: > 11 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 12 | DQYJKoZIhvcNAQEBBQAEggEAKgHKrPSMVhoh+PL2VEjGtz1oKP0kv6ZVmg6r 13 | TAGRmxhQkdraGAGOafOXtD7KVZqABHM0twqzsOQV/YIpZkNY7ZFTidmjwMOz 14 | KhREWiemKgFEV4lzwqsQVOeRJ3KloGRv8cx+AJGVOPPg38jGJl2tgr33KxZt 15 | s3dDYcz+f5cHzrUmOOueOI9CSwpzOc4slydifpbZNU3FNddhnwnbcz8gnie9 16 | Xp2Jbd8bnsaNVVLQcUR/pRI7JaonwK+Deb9+P3cq9nr9bethVVZBBwUZw9ny 17 | vZAaULGLu7x/cOnYmlfJXyouJrsBkUbKZCS/D8kC4lQBOm05DsJYsdXLSUm8 18 | tKqTjzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBTrnfaJnYOtPjOO0KY 19 | zJiHgCDgk/IrTi1LsdxthHY7A1Ubm9EUi3uzHfi8/e587yfjVQ==] 20 | -------------------------------------------------------------------------------- /features/plugin.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml plugins 2 | 3 | In order to encrypt data with various encryption methods 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to encrypt data in various ways 6 | 7 | Scenario: encrypt using plaintext plugin 8 | When I run `eyaml encrypt -n plaintext -o string -s hello` 9 | Then the stdout should contain exactly 'ENC[PLAINTEXT,aGVsbG8=]' 10 | 11 | Scenario: decrypt using plaintext plugin 12 | When I run `eyaml decrypt -n plaintext -s 'ENC[PLAINTEXT,aGVsbG8=]'` 13 | Then the stdout should contain exactly 'hello' 14 | 15 | Scenario: decrypt using inferred plugin 16 | When I run `eyaml decrypt -s 'ENC[PLAINTEXT,aGVsbG8=]'` 17 | Then the stdout should contain exactly 'hello' 18 | 19 | Scenario: decrypt using forced plaintext plugin 20 | When I run `eyaml decrypt -n plaintext -s 'ENC[aGVsbG8=]'` 21 | Then the stdout should contain exactly 'hello' 22 | 23 | Scenario: decrypt using two plugins 24 | When I run `eyaml decrypt -s 'ENC[PLAINTEXT,cmVkIGxvcnJ5IA==]ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAaezXCbx6WspcsKsCkgr9thLEckRppDvQyFloAHqswDNXllHxTSJDYlyoi96YvO96wazffdWO05TMs7HmkqJHkRzoTLGTdXSMz2Mu14QkUDe0zZyB0hl8qTbTcHzrw3ybUEJZEZ45Eenmr5VKuoBina7XJdIAXW8Ps4L/Dj7zsXlUxuyjDWu2WUd2X4gxO3W1SGfntk4OQ41NKXYKPIZLAXWMjC4VFh20tKXFwYhCpAanTBRNWgLBX3Dwg+c/l35EW8OQLfdaOQ30R/DgcoSsAZJveH3xqBv7UOes7vONLSYXTek6yFJBll7EuGbA/Mdw4gxd1qtCBdf48IiPPR0peTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCNWcGBqa8joAd0RMRzvx9VgBAf6PsvDZEa5cWdBaoTM/lP]'` 25 | Then the output should match /^red lorry blue lorry/ 26 | 27 | Scenario: decrypt using two plugins with default plaintext 28 | When I run `eyaml decrypt -n plaintext -s 'ENC[cmVkIGxvcnJ5IA==]ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAaezXCbx6WspcsKsCkgr9thLEckRppDvQyFloAHqswDNXllHxTSJDYlyoi96YvO96wazffdWO05TMs7HmkqJHkRzoTLGTdXSMz2Mu14QkUDe0zZyB0hl8qTbTcHzrw3ybUEJZEZ45Eenmr5VKuoBina7XJdIAXW8Ps4L/Dj7zsXlUxuyjDWu2WUd2X4gxO3W1SGfntk4OQ41NKXYKPIZLAXWMjC4VFh20tKXFwYhCpAanTBRNWgLBX3Dwg+c/l35EW8OQLfdaOQ30R/DgcoSsAZJveH3xqBv7UOes7vONLSYXTek6yFJBll7EuGbA/Mdw4gxd1qtCBdf48IiPPR0peTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCNWcGBqa8joAd0RMRzvx9VgBAf6PsvDZEa5cWdBaoTM/lP]'` 29 | Then the output should match /^red lorry blue lorry/ 30 | 31 | Scenario: decrypt using two plugins with default pkcs7 32 | When I run `eyaml decrypt -n pkcs7 -s 'ENC[PLAINTEXT,cmVkIGxvcnJ5IA==]ENC[MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAaezXCbx6WspcsKsCkgr9thLEckRppDvQyFloAHqswDNXllHxTSJDYlyoi96YvO96wazffdWO05TMs7HmkqJHkRzoTLGTdXSMz2Mu14QkUDe0zZyB0hl8qTbTcHzrw3ybUEJZEZ45Eenmr5VKuoBina7XJdIAXW8Ps4L/Dj7zsXlUxuyjDWu2WUd2X4gxO3W1SGfntk4OQ41NKXYKPIZLAXWMjC4VFh20tKXFwYhCpAanTBRNWgLBX3Dwg+c/l35EW8OQLfdaOQ30R/DgcoSsAZJveH3xqBv7UOes7vONLSYXTek6yFJBll7EuGbA/Mdw4gxd1qtCBdf48IiPPR0peTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCNWcGBqa8joAd0RMRzvx9VgBAf6PsvDZEa5cWdBaoTM/lP]'` 33 | Then the output should match /^red lorry blue lorry/ 34 | 35 | 36 | -------------------------------------------------------------------------------- /features/parser.feature: -------------------------------------------------------------------------------- 1 | Feature: Parser 2 | In order to more easily parse EYAML ENC blocks 3 | As a frustrated developer trying to enhance the edit mode 4 | I want to be given a set of tokens from EYAML input and our regex expressions 5 | 6 | Scenario: Parse with no regexs 7 | Given I make a parser instance with no regexs 8 | And I load a file called test_input.yaml 9 | When I parse the content 10 | Then I should have 1 token 11 | Then token 1 should be a NonMatchToken 12 | 13 | Scenario: Parse encrypted yaml 14 | Given I make a parser instance with the ENC regexs 15 | And I configure the keypair 16 | And I load a file called test_input.yaml 17 | When I parse the content 18 | Then I should have 35 tokens 19 | Then token 1 should be a NonMatchToken 20 | Then token 2 should be a EncToken 21 | Then token 2 should start with "ENC[PKCS7,MIIBiQYJKoZIhvcNAQ" 22 | Then token 2 should decrypt to start with "planet of the apes" 23 | Then token 2 should decrypt to a string with UTF-8 encodings 24 | 25 | Scenario: Parse encrypted yaml with keypair as envvars 26 | Given I make a parser instance with the ENC regexs 27 | And I configure the keypair using envvars 28 | And I load the keypair into envvars 29 | And I load a file called test_input.yaml 30 | When I parse the content 31 | Then I should have 35 tokens 32 | Then token 1 should be a NonMatchToken 33 | Then token 2 should be a EncToken 34 | Then token 2 should start with "ENC[PKCS7,MIIBiQYJKoZIhvcNAQ" 35 | Then token 2 should decrypt to start with "planet of the apes" 36 | Then token 2 should decrypt to a string with UTF-8 encodings 37 | 38 | Scenario: Parse decrypted yaml 39 | Given I make a parser instance with the DEC regexs 40 | And I configure the keypair using envvars 41 | And I load the keypair into envvars 42 | And I load a file called test_plain.yaml 43 | When I parse the content 44 | Then I should have 2 tokens 45 | Then token 1 should be a NonMatchToken 46 | Then token 2 should be a EncToken 47 | 48 | Scenario: Parse decrypted yaml with index 49 | Given I make a parser instance with the DEC regexs 50 | And I configure the keypair using envvars 51 | And I load the keypair into envvars 52 | And I load a file called test_plain_with_index.yaml 53 | When I parse the content 54 | Then I should have 5 tokens 55 | Then token 1 should be a NonMatchToken 56 | Then token 2 should be a EncToken 57 | Then token 2 id should be 23 58 | Then token 3 should be a NonMatchToken 59 | Then token 4 should be a EncToken 60 | Then token 4 id should be 24 61 | 62 | Scenario: Output indexed decryption tokens 63 | Given I make a parser instance with the ENC regexs 64 | And I configure the keypair using envvars 65 | And I load the keypair into envvars 66 | And I load a file called test_input.yaml 67 | When I parse the content 68 | And map it to index decrypted values 69 | Then decryption 1 should be "DEC(1)::PKCS7[planet of the apes]!" 70 | Then decryption 13 should be "DEC(13)::PKCS7[the count of monte cristo]!" 71 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/edithelper.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/logginghelper' 2 | 3 | class Hiera 4 | module Backend 5 | module Eyaml 6 | class EditHelper 7 | def self.find_editor 8 | editor = ENV.fetch('EDITOR', nil) 9 | editor ||= %w[/usr/bin/sensible-editor /usr/bin/editor /usr/bin/vim /usr/bin/vi].collect do |e| 10 | e if FileTest.executable? e 11 | end.compact.first 12 | raise StandardError, 'Editor not found. Please set your EDITOR env variable' if editor.nil? 13 | 14 | if editor.index(' ') 15 | editor = editor.dup if editor.frozen? # values from ENV are frozen 16 | editor.gsub!(/([^\\]|^)~/, '\1' + ENV.fetch('HOME', nil)) # replace ~ with home unless escaped 17 | editor.gsub!(/(^|[^\\])"/, '\1') # remove unescaped quotes during processing 18 | editor.gsub!('\\ ', ' ') # unescape spaces since we quote paths 19 | pieces = editor.split(' ') 20 | paths = # get possible paths, starting with longest 21 | pieces.each_with_index.map do |_, x| 22 | pieces[0..x].join(' ') 23 | end.reverse 24 | extensions = (ENV['PATHEXT'] || '').split(';') # handle Windows executables 25 | pathdirs = ENV['PATH'].split(File::PATH_SEPARATOR) 26 | paths += pathdirs.collect { |dir| paths.collect { |path| File.expand_path(path, dir) } }.flatten 27 | editorfile = paths.select do |path| 28 | FileTest.file?(path) || !extensions.select { |ext| FileTest.file?(path + ext) }.empty? 29 | end.first 30 | raise StandardError, 'Editor not found. Please set your EDITOR env variable' if editorfile.nil? 31 | 32 | raw_command = paths[(paths.index editorfile) % pieces.size] 33 | editor = "\"#{editorfile}\"#{editor[raw_command.size..-1]}" 34 | end 35 | editor 36 | end 37 | 38 | def self.secure_file_delete(args) 39 | file = File.open(args[:file], 'r+') 40 | num_bytes = args[:num_bytes] 41 | [0xff, 0x55, 0xaa, 0x00].each do |byte| 42 | file.seek(0, IO::SEEK_SET) 43 | num_bytes.times { file.print(byte.chr) } 44 | file.fsync 45 | end 46 | file.close 47 | File.delete args[:file] 48 | end 49 | 50 | def self.write_tempfile(data_to_write) 51 | file = Tempfile.open(['eyaml_edit', '.yaml']) 52 | path = file.path 53 | file.close! 54 | 55 | file = File.open(path, 'w') 56 | file.chmod(0o600) 57 | if ENV['OS'] == 'Windows_NT' 58 | # Windows doesn't support chmod 59 | icacls = 'C:\Windows\system32\icacls.exe' 60 | if File.executable? icacls 61 | current_user = `C:\\Windows\\system32\\whoami.exe`.chomp 62 | # Use ACLs to restrict access to the current user only 63 | command = %(#{icacls} "#{file.path}" /grant:r "#{current_user}":f /inheritance:r) 64 | system "#{command} >NUL 2>&1" 65 | end 66 | end 67 | file.puts data_to_write 68 | file.close 69 | 70 | LoggingHelper.debug "Wrote temporary file: #{path}" 71 | 72 | path 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Gem Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | build-release: 13 | # Prevent releases from forked repositories 14 | if: github.repository_owner == 'voxpupuli' 15 | name: Build the gem 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - name: Install Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 'ruby' 23 | - name: Build gem 24 | shell: bash 25 | run: gem build --verbose *.gemspec 26 | - name: Upload gem to GitHub cache 27 | uses: actions/upload-artifact@v6 28 | with: 29 | name: gem-artifact 30 | path: '*.gem' 31 | retention-days: 1 32 | compression-level: 0 33 | 34 | create-github-release: 35 | needs: build-release 36 | name: Create GitHub release 37 | runs-on: ubuntu-24.04 38 | permissions: 39 | contents: write # clone repo and create release 40 | steps: 41 | - name: Download gem from GitHub cache 42 | uses: actions/download-artifact@v7 43 | with: 44 | name: gem-artifact 45 | - name: Create Release 46 | shell: bash 47 | env: 48 | GH_TOKEN: ${{ github.token }} 49 | run: gh release create --repo ${{ github.repository }} ${{ github.ref_name }} --generate-notes *.gem 50 | 51 | release-to-github: 52 | needs: build-release 53 | name: Release to GitHub 54 | runs-on: ubuntu-24.04 55 | permissions: 56 | packages: write # publish to rubygems.pkg.github.com 57 | steps: 58 | - name: Download gem from GitHub cache 59 | uses: actions/download-artifact@v7 60 | with: 61 | name: gem-artifact 62 | - name: Publish gem to GitHub packages 63 | run: gem push --host https://rubygems.pkg.github.com/${{ github.repository_owner }} *.gem 64 | env: 65 | GEM_HOST_API_KEY: ${{ secrets.GITHUB_TOKEN }} 66 | 67 | release-to-rubygems: 68 | needs: build-release 69 | name: Release gem to rubygems.org 70 | runs-on: ubuntu-24.04 71 | environment: release # recommended by rubygems.org 72 | permissions: 73 | id-token: write # rubygems.org authentication 74 | steps: 75 | - name: Download gem from GitHub cache 76 | uses: actions/download-artifact@v7 77 | with: 78 | name: gem-artifact 79 | - uses: rubygems/configure-rubygems-credentials@v1.0.0 80 | - name: Publish gem to rubygems.org 81 | shell: bash 82 | run: gem push *.gem 83 | 84 | release-verification: 85 | name: Check that all releases are done 86 | runs-on: ubuntu-24.04 87 | permissions: 88 | contents: read # minimal permissions that we have to grant 89 | needs: 90 | - create-github-release 91 | - release-to-github 92 | - release-to-rubygems 93 | steps: 94 | - name: Download gem from GitHub cache 95 | uses: actions/download-artifact@v7 96 | with: 97 | name: gem-artifact 98 | - name: Install Ruby 99 | uses: ruby/setup-ruby@v1 100 | with: 101 | ruby-version: 'ruby' 102 | - name: Wait for release to propagate 103 | shell: bash 104 | run: | 105 | gem install rubygems-await 106 | gem await *.gem 107 | -------------------------------------------------------------------------------- /features/puppet.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml hiera integration 2 | 3 | In order to use eyaml as a hiera plugin 4 | As a developer using hiera-eyaml 5 | I want to verify that hiera-eyaml works within puppet and hiera 6 | 7 | Scenario: verify puppet with hiera can use hiera-eyaml to decrypt data 8 | When I run `bash -c 'rm -f /tmp/eyaml_puppettest.*' 2>/dev/null` 9 | When I run `puppet apply --disable_warnings deprecations --confdir ./puppet --node_name_value localhost puppet/manifests/init.pp` 10 | Then the output should contain "/tmp/eyaml_puppettest" 11 | Then the file "/tmp/eyaml_puppettest.1" should match /^good night$/ 12 | Then the file "/tmp/eyaml_puppettest.2" should match /^and good luck$/ 13 | Then the file "/tmp/eyaml_puppettest.3" should match /^and good luck$/ 14 | Then the file "/tmp/eyaml_puppettest.4" should match /^and good luck$/ 15 | Then the file "/tmp/eyaml_puppettest.5" should match /^gangs of new york$/ 16 | 17 | 18 | Scenario: verify puppet with hiera can use hiera-eyaml to decrypt data with keys as environment variables 19 | Given I load the keypair into envvars 20 | When I run `bash -c 'rm -f /tmp/eyaml_puppettest.*' 2>/dev/null` 21 | When I run `puppet apply --disable_warnings deprecations --confdir ./puppet-envvar --node_name_value localhost puppet-envvar/manifests/init.pp` 22 | Then the output should contain "/tmp/eyaml_puppettest" 23 | Then the file "/tmp/eyaml_puppettest.1" should match /^good night$/ 24 | Then the file "/tmp/eyaml_puppettest.2" should match /^and good luck$/ 25 | Then the file "/tmp/eyaml_puppettest.3" should match /^and good luck$/ 26 | Then the file "/tmp/eyaml_puppettest.4" should match /^and good luck$/ 27 | Then the file "/tmp/eyaml_puppettest.5" should match /^gangs of new york$/ 28 | 29 | 30 | Scenario: verify puppet and facter for correct hash merge with incorrect fact 31 | Given I set FACTER_fact to "not-existcity" 32 | When I run `bash -c 'rm -f /tmp/eyaml_puppettest.*' 2>/dev/null` 33 | When I run `puppet apply --disable_warnings deprecations --confdir ./puppet-hiera-merge --node_name_value localhost puppet-hiera-merge/manifests/init.pp` 34 | Then the output should contain "/tmp/eyaml_puppettest" 35 | Then the file "/tmp/eyaml_puppettest.1" should match /^good night$/ 36 | Then the file "/tmp/eyaml_puppettest.2" should match /^great to see you$/ 37 | Then the file "/tmp/eyaml_puppettest.3" should match /good luck/ 38 | Then the file "/tmp/eyaml_puppettest.4" should match /"here": "we go again!"/ 39 | Then the file "/tmp/eyaml_puppettest.5" should match /^gangs of new york\nis to the warriors$/ 40 | 41 | Scenario: verify puppet and facter for correct hash merge 42 | Given I set FACTER_fact to "city" 43 | When I run `bash -c 'rm -f /tmp/eyaml_puppettest.*' 2>/dev/null` 44 | When I run `puppet apply --disable_warnings deprecations --confdir ./puppet-hiera-merge --node_name_value localhost puppet-hiera-merge/manifests/init.pp` 45 | Then the output should contain "/tmp/eyaml_puppettest" 46 | Then the file "/tmp/eyaml_puppettest.1" should match /^rise and shine$/ 47 | Then the file "/tmp/eyaml_puppettest.2" should match /^break a leg$/ 48 | Then the file "/tmp/eyaml_puppettest.3" should match /it'll be alright on the night/ 49 | Then the file "/tmp/eyaml_puppettest.4" should match /"here": "be rabbits"/ 50 | Then the file "/tmp/eyaml_puppettest.4" should match /"see": "no evil"/ 51 | Then the file "/tmp/eyaml_puppettest.5" should match /^source code\nis to donny darko$/ 52 | 53 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/decrypt.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml' 2 | require 'hiera/backend/eyaml/utils' 3 | require 'hiera/backend/eyaml/options' 4 | require 'hiera/backend/eyaml/parser/parser' 5 | require 'hiera/backend/eyaml/subcommand' 6 | 7 | class Hiera 8 | module Backend 9 | module Eyaml 10 | module Subcommands 11 | class Decrypt < Subcommand 12 | def self.options 13 | [{ name: :string, 14 | description: 'Source input is a string provided as an argument', 15 | short: 's', 16 | type: :string, }, 17 | { name: :file, 18 | description: 'Source input is a regular file', 19 | short: 'f', 20 | type: :string, }, 21 | { name: :eyaml, 22 | description: 'Source input is an eyaml file', 23 | short: 'e', 24 | type: :string, }, 25 | { name: :stdin, 26 | description: 'Source input is taken from stdin', 27 | short: :none, },] 28 | end 29 | 30 | def self.description 31 | 'decrypt some data' 32 | end 33 | 34 | def self.validate(options) 35 | sources = %i[eyaml password string file stdin].collect { |x| x if options[x] }.compact 36 | Optimist.die 'You must specify a source' if sources.count.zero? 37 | Optimist.die "You can only specify one of (#{sources.join(', ')})" if sources.count > 1 38 | options[:source] = sources.first 39 | 40 | options[:input_data] = case options[:source] 41 | when :stdin 42 | STDIN.read 43 | when :string 44 | options[:string] 45 | when :file 46 | File.read options[:file] 47 | when :eyaml 48 | File.read options[:eyaml] 49 | end 50 | options 51 | end 52 | 53 | def self.execute 54 | parser = Parser::ParserFactory.encrypted_parser 55 | tokens = parser.parse(Eyaml::Options[:input_data]) 56 | case Eyaml::Options[:source] 57 | when :eyaml 58 | decrypted = tokens.map { |token| token.to_decrypted } 59 | decrypted.join 60 | else 61 | yamled = false 62 | decrypted = tokens.map do |token| 63 | case token.class.name 64 | when /::EncToken$/ 65 | if yamled 66 | yamled = false 67 | if /[\r\n]/.match?(token.to_plain_text) 68 | "|\n " + token.to_plain_text.gsub(/([\r\n]+)/, 69 | '\1 ') 70 | else 71 | token.to_plain_text 72 | end 73 | else 74 | token.to_plain_text 75 | end 76 | else 77 | yamled = true 78 | token.match 79 | end 80 | end 81 | decrypted.join 82 | end 83 | end 84 | 85 | def self.print_out(string) 86 | case Eyaml::Options[:source] 87 | when :eyaml 88 | # Be sure the output ends with a newline, since YAML is a text format. 89 | puts string 90 | else 91 | # Print the exact result. 92 | print string 93 | end 94 | end 95 | end 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /features/decrypts.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml decrypting 2 | 3 | In order to decrypt data 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to decrypt data in various ways 6 | 7 | Scenario: decrypt a simple string 8 | When I run `eyaml decrypt -s 'ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAJsIbL+DE4b5mbT4ozzsGximhweXkJakBXRNqi/TOBV5RnMjlAhik2NQGyZdrg4fmQynyQvI7lKPos5bmqT6Ltk0XxfL0l1x8MIdVLDu7JG72a+5bbU7EK/PlhwaeJ0QpnA0M34koNykSmcvdVUoIDag71lqw4Hmk8JQuXmo95gP0sXHvlahgxR522Z8MTEitfimtZPnHJVMjQEnBHIwXLkEfqUHH3RciED3AkeU+En63Ou7Nq1SqoC4ln0oL1L2gRqsyEKWF/cigcZfAggh9rva6wlthh+Fj7yJy7ALVG/AXwrF1sJfTR31+RMbzPbgOHXES8P4yQvQnx6LNUSKAgDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCSuNScp5k8F0RKt63fkoPrgCBYbnl44Wdv5mtaJmJcc0WIUcLTpixl1IY2uQ7IiI358w==]'` 9 | Then the output should match /^one flew over the cuckoos nest$/ 10 | 11 | Scenario: decrypt a default encryption string 12 | When I run `eyaml decrypt -s 'ENC[MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAJsIbL+DE4b5mbT4ozzsGximhweXkJakBXRNqi/TOBV5RnMjlAhik2NQGyZdrg4fmQynyQvI7lKPos5bmqT6Ltk0XxfL0l1x8MIdVLDu7JG72a+5bbU7EK/PlhwaeJ0QpnA0M34koNykSmcvdVUoIDag71lqw4Hmk8JQuXmo95gP0sXHvlahgxR522Z8MTEitfimtZPnHJVMjQEnBHIwXLkEfqUHH3RciED3AkeU+En63Ou7Nq1SqoC4ln0oL1L2gRqsyEKWF/cigcZfAggh9rva6wlthh+Fj7yJy7ALVG/AXwrF1sJfTR31+RMbzPbgOHXES8P4yQvQnx6LNUSKAgDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCSuNScp5k8F0RKt63fkoPrgCBYbnl44Wdv5mtaJmJcc0WIUcLTpixl1IY2uQ7IiI358w==]'` 13 | Then the output should match /^one flew over the cuckoos nest$/ 14 | 15 | Scenario: decrypt an encrypted file 16 | When I run `eyaml decrypt -f test_input.encrypted.txt` 17 | Then the output should match /^danger will robinson$/ 18 | 19 | Scenario: decrypt an eyaml file 20 | When I run `eyaml decrypt -e test_input.yaml` 21 | Then the output should match /encrypted_string: DEC::PKCS7\[planet of the apes\]\!/ 22 | And the output should match /encrypted_block: >\n\s+DEC::PKCS7\[gangs of new york\]\!/ 23 | And the output should match /encrypted_tabbed_block: >\n\s+DEC::PKCS7\[gangs of new york\]\!/ 24 | And the output should match /\- DEC::PKCS7\[apocalypse now\]\!/ 25 | And the output should match /\- DEC::PKCS7\[the count of monte cristo\]\!/ 26 | And the output should match /\- array4/ 27 | And the output should match /\- DEC::PKCS7\[dr strangelove\]\!/ 28 | And the output should match /\- array5/ 29 | And the output should match /\- >\n\s+DEC::PKCS7\[kramer vs kramer\]\!/ 30 | And the output should match /\- >\n\s+DEC::PKCS7\[the manchurian candidate\]\!/ 31 | And the output should match /\- >\n\s+tomorrow and tomorrow and\s*\n\s+tomorrow creeps/ 32 | And the output should match /\- >\n\s+DEC::PKCS7\[much ado about nothing\]\!/ 33 | And the output should match /\- >\n\s+when shall we three meet again\n\s+in thunder/ 34 | And the output should match /\- DEC::PKCS7\[the english patient\]\!/ 35 | And the output should match /\- >\n\s+DEC::PKCS7\[the pink panther\]\!/ 36 | And the output should match /\- >\n\s+i wondered lonely\s*\n\s+as a cloud/ 37 | And the output should match /\s+key5: DEC::PKCS7\[value5\]\!/ 38 | And the output should match /\s+key6: DEC::PKCS7\[value6\]\!/ 39 | 40 | Scenario: decrypt using STDIN 41 | When I run `eyaml decrypt --stdin` interactively 42 | And I type "ENC[MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAJsIbL+DE4b5mbT4ozzsGximhweXkJakBXRNqi/TOBV5RnMjlAhik2NQGyZdrg4fmQynyQvI7lKPos5bmqT6Ltk0XxfL0l1x8MIdVLDu7JG72a+5bbU7EK/PlhwaeJ0QpnA0M34koNykSmcvdVUoIDag71lqw4Hmk8JQuXmo95gP0sXHvlahgxR522Z8MTEitfimtZPnHJVMjQEnBHIwXLkEfqUHH3RciED3AkeU+En63Ou7Nq1SqoC4ln0oL1L2gRqsyEKWF/cigcZfAggh9rva6wlthh+Fj7yJy7ALVG/AXwrF1sJfTR31+RMbzPbgOHXES8P4yQvQnx6LNUSKAgDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCSuNScp5k8F0RKt63fkoPrgCBYbnl44Wdv5mtaJmJcc0WIUcLTpixl1IY2uQ7IiI358w==]" 43 | And I close the stdin stream 44 | Then the output should match /^one flew over the cuckoos nest$/ 45 | -------------------------------------------------------------------------------- /features/step_definitions/parser_steps.rb: -------------------------------------------------------------------------------- 1 | Given(/^I make a parser instance with no regexs$/) do 2 | @parser = Hiera::Backend::Eyaml::Parser::Parser.new([]) 3 | end 4 | 5 | Given(/^I make a parser instance with the ENC regexs$/) do 6 | @parser = Hiera::Backend::Eyaml::Parser::ParserFactory.encrypted_parser 7 | end 8 | 9 | Given(/^I make a parser instance with the DEC regexs$/) do 10 | @parser = Hiera::Backend::Eyaml::Parser::ParserFactory.decrypted_parser 11 | end 12 | 13 | And(/^I load a file called (.*)$/) do |file| 14 | @content = File.read("features/sandbox/#{file}") 15 | end 16 | 17 | And(/^I configure the keypair$/) do 18 | Hiera::Backend::Eyaml::Options[:pkcs7_public_key] = 'features/sandbox/keys/public_key.pkcs7.pem' 19 | Hiera::Backend::Eyaml::Options[:pkcs7_private_key] = 'features/sandbox/keys/private_key.pkcs7.pem' 20 | Hiera::Backend::Eyaml::Options[:pkcs7_public_key_env_var] = nil 21 | Hiera::Backend::Eyaml::Options[:pkcs7_private_key_env_var] = nil 22 | # This needs to carry over to the later steps, so must modify modify both the 23 | # fake ENV state and the real ENV state. 24 | delete_environment_variable 'EYAML_PUBLIC_KEY' 25 | delete_environment_variable 'EYAML_PRIVATE_KEY' 26 | ENV['EYAML_PUBLIC_KEY'] = '' 27 | ENV['EYAML_PRIVATE_KEY'] = '' 28 | end 29 | 30 | And(/^I configure the keypair using envvars$/) do 31 | Hiera::Backend::Eyaml::Options[:pkcs7_public_key] = nil 32 | Hiera::Backend::Eyaml::Options[:pkcs7_private_key] = nil 33 | Hiera::Backend::Eyaml::Options[:pkcs7_public_key_env_var] = 'EYAML_PUBLIC_KEY' 34 | Hiera::Backend::Eyaml::Options[:pkcs7_private_key_env_var] = 'EYAML_PRIVATE_KEY' 35 | end 36 | 37 | And(/^I load the keypair into envvars$/) do 38 | d = aruba.config.root_directory 39 | # Validate that the files exist 40 | pubkeyfile = File.join(d, 'features', 'sandbox', 'keys', 'public_key.pkcs7.pem') 41 | privkeyfile = File.join(d, 'features', 'sandbox', 'keys', 'private_key.pkcs7.pem') 42 | expect(File.exist?(pubkeyfile)).to be_truthy 43 | expect(File.exist?(privkeyfile)).to be_truthy 44 | 45 | # Load the files and validate 46 | pubkey = File.read(pubkeyfile) 47 | privkey = File.read(privkeyfile) 48 | expect(pubkey).not_to be_empty 49 | expect(privkey).not_to be_empty 50 | 51 | # Use keys 52 | # This needs to carry over to the later steps, so must modify modify both the 53 | # fake ENV state and the real ENV state. 54 | set_environment_variable 'EYAML_PUBLIC_KEY', pubkey 55 | set_environment_variable 'EYAML_PRIVATE_KEY', privkey 56 | ENV['EYAML_PUBLIC_KEY'] = pubkey 57 | ENV['EYAML_PRIVATE_KEY'] = privkey 58 | end 59 | 60 | When(/^I parse the content$/) do 61 | @tokens = @parser.parse @content 62 | end 63 | 64 | Then(/^I should have (\d+) tokens?$/) do |number_of_tokens| 65 | expect(@tokens.size).to eq(number_of_tokens.to_i) 66 | end 67 | 68 | Then(/^token (\d+) should be a (.*)$/) do |index, class_name| 69 | actual_class_name = @tokens[index.to_i - 1].class.name 70 | expect(actual_class_name.split('::').last).to eq class_name 71 | end 72 | 73 | Then(/^token (\d+) should start with "(.*)"$/) do |index, content| 74 | token = @tokens[index.to_i - 1] 75 | expect(token.match).to match(/^#{Regexp.escape(content)}/) 76 | end 77 | 78 | Then(/^token (\d+) should decrypt to start with "(.*)"$/) do |index, plain| 79 | token = @tokens[index.to_i - 1] 80 | expect(token.plain_text).to match(/^#{Regexp.escape(plain)}/) 81 | end 82 | 83 | Then(/^token (\d+) should decrypt to a string with UTF-8 encodings$/) do |index| 84 | token = @tokens[index.to_i - 1] 85 | expect(token.plain_text.encoding.to_s).to eq 'UTF-8' 86 | end 87 | 88 | And(/^map it to index decrypted values$/) do 89 | @decrypted = @tokens.each_with_index.to_a.map do |(t, index)| 90 | t.to_decrypted index: index 91 | end 92 | end 93 | 94 | Then(/^decryption (\d+) should be "(.*)"$/) do |index, content| 95 | decrypted = @decrypted[index.to_i] 96 | expect(decrypted).to eq content 97 | end 98 | 99 | Then(/^token (\d+) id should be (\d+)$/) do |index, token_id| 100 | token = @tokens[index.to_i - 1] 101 | expect(token.id).to eq(token_id.to_i) 102 | end 103 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/encrypt.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/options' 2 | require 'hiera/backend/eyaml/parser/parser' 3 | require 'hiera/backend/eyaml/parser/encrypted_tokens' 4 | require 'hiera/backend/eyaml/subcommand' 5 | 6 | class Hiera 7 | module Backend 8 | module Eyaml 9 | module Subcommands 10 | class Encrypt < Subcommand 11 | def self.options 12 | [{ name: :password, 13 | description: 'Source input is a password entered on the terminal', 14 | short: 'p', }, 15 | { name: :string, 16 | description: 'Source input is a string provided as an argument', 17 | short: 's', 18 | type: :string, }, 19 | { name: :file, 20 | description: 'Source input is a regular file', 21 | short: 'f', 22 | type: :string, }, 23 | { name: :stdin, 24 | description: 'Source input is taken from stdin', 25 | short: :none, }, 26 | { name: :eyaml, 27 | description: 'Source input is an eyaml file', 28 | short: 'e', 29 | type: :string, }, 30 | { name: :output, 31 | description: 'Output format of final result (examples, block, string)', 32 | type: :string, 33 | short: 'o', 34 | default: 'examples', }, 35 | { name: :label, 36 | description: 'Apply a label to the encrypted result', 37 | short: 'l', 38 | type: :string, },] 39 | end 40 | 41 | def self.description 42 | 'encrypt some data' 43 | end 44 | 45 | def self.validate(options) 46 | sources = %i[password string file stdin eyaml].collect { |x| x if options[x] }.compact 47 | Optimist.die 'You must specify a source' if sources.count.zero? 48 | Optimist.die "You can only specify one of (#{sources.join(', ')})" if sources.count > 1 49 | options[:source] = sources.first 50 | 51 | options[:input_data] = case options[:source] 52 | when :password 53 | require 'hiera/backend/eyaml/highlinehelper' 54 | HighlineHelper.read_password 55 | when :string 56 | options[:string] 57 | when :file 58 | File.read options[:file] 59 | when :stdin 60 | STDIN.read 61 | when :eyaml 62 | File.read options[:eyaml] 63 | end 64 | options 65 | end 66 | 67 | def self.execute 68 | case Eyaml::Options[:source] 69 | when :eyaml 70 | parser = Parser::ParserFactory.decrypted_parser 71 | tokens = parser.parse(Eyaml::Options[:input_data]) 72 | encrypted = tokens.map { |token| token.to_encrypted } 73 | encrypted.join 74 | else 75 | encryptor = Encryptor.find 76 | ciphertext = encryptor.encode(encryptor.encrypt(Eyaml::Options[:input_data])) 77 | token = Parser::EncToken.new(:block, Eyaml::Options[:input_data], encryptor, ciphertext, nil, ' ') 78 | case Eyaml::Options[:output] 79 | when 'block' 80 | token.to_encrypted label: Eyaml::Options[:label], use_chevron: !Eyaml::Options[:label].nil?, 81 | format: :block 82 | when 'string' 83 | token.to_encrypted label: Eyaml::Options[:label], format: :string 84 | when 'examples' 85 | string = token.to_encrypted label: Eyaml::Options[:label] || 'string', format: :string 86 | block = token.to_encrypted label: Eyaml::Options[:label] || 'block', format: :block 87 | "#{string}\n\nOR\n\n#{block}" 88 | else 89 | token.to_encrypted format: :string 90 | end 91 | end 92 | end 93 | 94 | def self.print_out(string) 95 | case Eyaml::Options[:output] 96 | when 'string' 97 | # Do not include a newline, so that 'eyaml decrypt' of the 98 | # output returns the original input. 99 | print string 100 | else 101 | # The output is a text file, so ensure there is a final newline. 102 | puts string 103 | end 104 | end 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/encryptors/pkcs7.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | require 'hiera/backend/eyaml/encryptor' 3 | require 'hiera/backend/eyaml/encrypthelper' 4 | require 'hiera/backend/eyaml/logginghelper' 5 | require 'hiera/backend/eyaml/options' 6 | 7 | class Hiera 8 | module Backend 9 | module Eyaml 10 | module Encryptors 11 | class Pkcs7 < Encryptor 12 | self.options = { 13 | private_key: { desc: 'Path to private key', 14 | type: :string, 15 | default: './keys/private_key.pkcs7.pem', }, 16 | public_key: { desc: 'Path to public key', 17 | type: :string, 18 | default: './keys/public_key.pkcs7.pem', }, 19 | private_key_env_var: { desc: 'Name of environment variable to read private key from', 20 | type: :string, }, 21 | public_key_env_var: { desc: 'Name of environment variable to read public key from', 22 | type: :string, }, 23 | keysize: { desc: 'Key size used for encryption', 24 | type: :integer, 25 | default: 2048, }, 26 | } 27 | 28 | self.tag = 'PKCS7' 29 | 30 | def self.encrypt(plaintext) 31 | LoggingHelper.trace 'PKCS7 encrypt' 32 | 33 | public_key_pem = load_public_key_pem 34 | if public_key_pem.include? 'BEGIN CERTIFICATE' 35 | public_key_x509 = OpenSSL::X509::Certificate.new(public_key_pem) 36 | elsif public_key_pem.include? 'BEGIN PUBLIC KEY' 37 | public_key_rsa = OpenSSL::PKey::RSA.new(public_key_pem) 38 | public_key_x509 = OpenSSL::X509::Certificate.new 39 | public_key_x509.public_key = public_key_rsa.public_key 40 | else 41 | raise StandardError, "file #{public_key_pem} cannot be used to encrypt - invalid public key format" 42 | end 43 | 44 | cipher = OpenSSL::Cipher.new('aes-256-cbc') 45 | OpenSSL::PKCS7.encrypt([public_key_x509], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der 46 | end 47 | 48 | def self.decrypt(ciphertext) 49 | LoggingHelper.trace 'PKCS7 decrypt' 50 | 51 | private_key_pem = load_private_key_pem 52 | private_key_rsa = OpenSSL::PKey::RSA.new(private_key_pem) 53 | 54 | pkcs7 = OpenSSL::PKCS7.new(ciphertext) 55 | 56 | public_key_x509 = OpenSSL::X509::Certificate.new 57 | public_key_x509.serial = pkcs7.recipients[0].serial 58 | public_key_x509.issuer = pkcs7.recipients[0].issuer 59 | public_key_x509.public_key = private_key_rsa.public_key 60 | 61 | pkcs7.decrypt(private_key_rsa, public_key_x509) 62 | end 63 | 64 | def self.create_keys 65 | # Do equivalent of: 66 | # openssl req -x509 -nodes -newkey rsa:2048 -keyout privatekey.pem -out publickey.pem -batch 67 | 68 | public_key = option :public_key 69 | private_key = option :private_key 70 | keysize = option :keysize 71 | 72 | key = OpenSSL::PKey::RSA.new(keysize) 73 | EncryptHelper.ensure_key_dir_exists private_key 74 | EncryptHelper.write_important_file filename: private_key, content: key.to_pem, mode: 0o600 75 | 76 | cert = OpenSSL::X509::Certificate.new 77 | # In JRuby implementation of openssl, not_before and not_after 78 | # are required to sign cert with key and digest. Signing the 79 | # certificate is only required for Ruby 2.7 to call cert.to_pem. 80 | cert.not_before = Time.now 81 | cert.not_after = if 1.size == 8 # 64bit 82 | Time.now + (50 * 365 * 24 * 60 * 60) 83 | else # 32bit 84 | Time.at(0x7fffffff) 85 | end 86 | cert.public_key = key.public_key 87 | cert.sign key, OpenSSL::Digest.new('SHA256') 88 | 89 | EncryptHelper.ensure_key_dir_exists public_key 90 | EncryptHelper.write_important_file filename: public_key, content: cert.to_pem 91 | LoggingHelper.info 'Keys created OK' 92 | end 93 | 94 | def self.load_ANY_key_pem(optname_key, optname_env_var) 95 | opt_key = option(optname_key.to_sym) 96 | opt_key_env_var = option(optname_env_var.to_sym) 97 | 98 | if opt_key and opt_key_env_var 99 | warn "both #{optname_key} and #{optname_env_var} specified, using #{optname_env_var}" 100 | end 101 | 102 | if opt_key_env_var 103 | raise StandardError, "env #{opt_key_env_var} is not set" unless ENV[opt_key_env_var] 104 | 105 | opt_key_pem = ENV.fetch(opt_key_env_var, nil) 106 | elsif opt_key 107 | raise StandardError, "file #{opt_key} does not exist" unless File.exist? opt_key 108 | 109 | opt_key_pem = File.read opt_key 110 | else 111 | raise StandardError, "pkcs7_#{optname_key} is not defined" unless opt_key or opt_key_env_var 112 | end 113 | 114 | opt_key_pem 115 | end 116 | 117 | def self.load_public_key_pem 118 | load_ANY_key_pem('public_key', 'public_key_env_var') 119 | end 120 | 121 | def self.load_private_key_pem 122 | load_ANY_key_pem('private_key', 'private_key_env_var') 123 | end 124 | end 125 | end 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml_backend.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/encryptor' 2 | require 'hiera/backend/eyaml/utils' 3 | require 'hiera/backend/eyaml/options' 4 | require 'hiera/backend/eyaml/parser/parser' 5 | require 'hiera/filecache' 6 | 7 | require 'yaml' 8 | 9 | class Hiera 10 | module Backend 11 | class Eyaml_backend 12 | attr_reader :extension 13 | 14 | def initialize(cache = nil) 15 | debug('Hiera eYAML backend starting') 16 | 17 | @decrypted_cache = {} 18 | @cache = cache || Filecache.new 19 | @extension = Config[:eyaml][:extension] || 'eyaml' 20 | end 21 | 22 | def lookup(key, scope, order_override, resolution_type) 23 | answer = nil 24 | 25 | parse_options(scope) 26 | 27 | debug("Looking up #{key} in eYAML backend") 28 | 29 | Backend.datasources(scope, order_override) do |source| 30 | debug("Looking for data source #{source}") 31 | eyaml_file = Backend.datafile(:eyaml, scope, source, extension) || next 32 | 33 | next unless File.exist?(eyaml_file) 34 | 35 | data = @cache.read(eyaml_file, Hash) do |data| 36 | YAML.load(data) || {} 37 | end 38 | 39 | next if data.empty? 40 | next unless data.include?(key) 41 | 42 | # Extra logging that we found the key. This can be outputted 43 | # multiple times if the resolution type is array or hash but that 44 | # should be expected as the logging will then tell the user ALL the 45 | # places where the key is found. 46 | debug("Found #{key} in #{source}") 47 | 48 | # for array resolution we just append to the array whatever 49 | # we find, we then goes onto the next file and keep adding to 50 | # the array 51 | # 52 | # for priority searches we break after the first found data item 53 | new_answer = parse_answer(data[key], scope) 54 | case resolution_type 55 | when :array 56 | unless new_answer.is_a? Array or new_answer.is_a? String 57 | raise Exception, 58 | "Hiera type mismatch: expected Array and got #{new_answer.class}" 59 | end 60 | 61 | answer ||= [] 62 | answer << new_answer 63 | when :hash 64 | unless new_answer.is_a? Hash 65 | raise Exception, 66 | "Hiera type mismatch: expected Hash and got #{new_answer.class}" 67 | end 68 | 69 | answer ||= {} 70 | answer = Backend.merge_answer(new_answer, answer) 71 | else 72 | answer = new_answer 73 | break 74 | end 75 | end 76 | 77 | answer 78 | end 79 | 80 | private 81 | 82 | def debug(message) 83 | Hiera.debug("[eyaml_backend]: #{message}") 84 | end 85 | 86 | def decrypt(data) 87 | if encrypted?(data) 88 | debug('Attempting to decrypt') 89 | begin 90 | parser = Eyaml::Parser::ParserFactory.hiera_backend_parser 91 | tokens = parser.parse(data) 92 | decrypted = tokens.map { |token| token.to_plain_text } 93 | plaintext = decrypted.join 94 | rescue OpenSSL::PKCS7::PKCS7Error => e 95 | debug("Caught exception: #{e.class}, #{e.message}\n" \ 96 | "#{e.backtrace.join("\n")}") 97 | raise 'Hiera-eyaml decryption failed, check the ' \ 98 | "encrypted data matches the key you are using.\n" \ 99 | "Raw message from system: #{e.message}" 100 | end 101 | plaintext.chomp 102 | else 103 | data 104 | end 105 | end 106 | 107 | def encrypted?(data) 108 | /.*ENC\[.*\]/.match?(data) || false 109 | end 110 | 111 | def parse_answer(data, scope, extra_data = {}) 112 | if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass) 113 | data 114 | elsif data.is_a?(String) 115 | parse_string(data, scope, extra_data) 116 | elsif data.is_a?(Hash) 117 | answer = {} 118 | data.each_pair do |key, val| 119 | interpolated_key = Backend.parse_string(key, scope, extra_data) 120 | answer[interpolated_key] = parse_answer(val, scope, extra_data) 121 | end 122 | 123 | answer 124 | elsif data.is_a?(Array) 125 | answer = [] 126 | data.each do |item| 127 | answer << parse_answer(item, scope, extra_data) 128 | end 129 | 130 | answer 131 | end 132 | end 133 | 134 | def parse_options(scope) 135 | Config[:eyaml].each do |key, value| 136 | parsed_value = Backend.parse_string(value, scope) 137 | Eyaml::Options[key] = parsed_value 138 | debug("Set option: #{key} = #{parsed_value}") 139 | end 140 | 141 | Eyaml::Options[:source] = 'hiera' 142 | end 143 | 144 | def parse_string(data, scope, extra_data = {}) 145 | if Eyaml::Options[:cache_decrypted] 146 | if @decrypted_cache.include?(data) 147 | debug('Retrieving data from decrypted cache') 148 | decrypted_data = @decrypted_cache[data] 149 | else 150 | decrypted_data = decrypt(data) 151 | debug('Adding data to decrypted cache') 152 | @decrypted_cache[data] = decrypted_data 153 | end 154 | else 155 | decrypted_data = decrypt(data) 156 | end 157 | 158 | Backend.parse_string(decrypted_data, scope, extra_data) 159 | end 160 | end 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommand.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | require 'yaml' 3 | # require 'hiera/backend/eyaml/subcommands/unknown_command' 4 | 5 | class Hiera 6 | module Backend 7 | module Eyaml 8 | class Subcommand 9 | class << self 10 | attr_accessor :global_options, :options, :helptext 11 | end 12 | 13 | @@global_options = [ 14 | { name: :encrypt_method, 15 | description: 'Override default encryption and decryption method (default is PKCS7)', 16 | short: 'n', 17 | default: 'pkcs7', }, 18 | { name: :version, 19 | description: 'Show version information', }, 20 | { name: :verbose, 21 | description: 'Be more verbose', 22 | short: 'v', }, 23 | { name: :trace, 24 | description: 'Enable trace debug', 25 | short: 't', }, 26 | { name: :quiet, 27 | description: 'Be less verbose', 28 | short: 'q', }, 29 | { name: :help, 30 | description: 'Information on how to use this command', 31 | short: 'h', }, 32 | ] 33 | 34 | def self.load_config_file 35 | config = { options: {}, sources: [] } 36 | 37 | config_paths = [] 38 | # Global 39 | config_paths += ['/etc/eyaml/config.yaml'] 40 | # Home directory 41 | env_home = ENV.fetch('HOME', nil) 42 | config_paths += ["#{env_home}/.eyaml/config.yaml"] if env_home 43 | # Relative to current directory 44 | config_paths += ['.eyaml/config.yaml'] 45 | # Explicit ENV variable. 46 | env_eyaml_config = ENV.fetch('EYAML_CONFIG', nil) 47 | config_paths += [env_eyaml_config] if env_eyaml_config 48 | 49 | # Load each path and stack configs. 50 | config_paths.each do |config_file| 51 | next unless config_file and File.file? config_file 52 | 53 | begin 54 | yaml_contents = YAML.load_file(config_file) 55 | config[:options].merge! yaml_contents 56 | config[:sources].push(config_file) 57 | rescue StandardError 58 | raise StandardError, "Could not open config file \"#{config_file}\" for reading" 59 | end 60 | end 61 | config 62 | end 63 | 64 | def self.all_options 65 | options = @@global_options.dup 66 | options += self.options if self.options 67 | options += Plugins.options 68 | # merge in defaults from configuration files 69 | config_file = load_config_file 70 | options.map! do |opt| 71 | key_name = "#{opt[:name]}" 72 | if config_file[:options].has_key? key_name 73 | opt[:default] = config_file[:options][key_name] 74 | opt 75 | else 76 | opt 77 | end 78 | end 79 | { options: options, sources: config_file[:sources] || [] } 80 | end 81 | 82 | def self.attach_option(opt) 83 | self.suboptions += opt 84 | end 85 | 86 | def self.find(commandname = 'unknown_command') 87 | begin 88 | require "hiera/backend/eyaml/subcommands/#{commandname.downcase}" 89 | rescue Exception 90 | require 'hiera/backend/eyaml/subcommands/unknown_command' 91 | return Hiera::Backend::Eyaml::Subcommands::UnknownCommand 92 | end 93 | command_module = Module.const_get(:Hiera).const_get(:Backend).const_get(:Eyaml).const_get(:Subcommands) 94 | command_class = Utils.find_closest_class parent_class: command_module, class_name: commandname 95 | command_class || Hiera::Backend::Eyaml::Subcommands::UnknownCommand 96 | end 97 | 98 | def self.parse 99 | me = self 100 | all = all_options 101 | 102 | options = Optimist.options do 103 | version 'Hiera-eyaml version ' + Hiera::Backend::Eyaml::VERSION.to_s 104 | banner ["eyaml #{me.prettyname}: #{me.description}", me.helptext, 'Options:'].compact.join("\n\n") 105 | 106 | all[:options].each do |available_option| 107 | skeleton = { description: '', 108 | short: :none, } 109 | 110 | skeleton.merge! available_option 111 | opt skeleton[:name], 112 | skeleton[:desc] || skeleton[:description], # legacy plugins 113 | short: skeleton[:short], 114 | default: skeleton[:default], 115 | type: skeleton[:type] 116 | end 117 | 118 | stop_on Eyaml.subcommands 119 | end 120 | 121 | Hiera::Backend::Eyaml.verbosity_level += 1 if options[:verbose] 122 | 123 | Hiera::Backend::Eyaml.verbosity_level += 2 if options[:trace] 124 | 125 | Hiera::Backend::Eyaml.verbosity_level = 0 if options[:quiet] 126 | 127 | Hiera::Backend::Eyaml.default_encryption_scheme = options[:encrypt_method] if options[:encrypt_method] 128 | 129 | if all[:sources] 130 | all[:sources].each do |source| 131 | LoggingHelper.debug "Loaded config from #{source}" 132 | end 133 | end 134 | 135 | options 136 | end 137 | 138 | def self.print_out(string) 139 | print string 140 | end 141 | 142 | def self.validate(args) 143 | args 144 | end 145 | 146 | def self.description 147 | 'no description' 148 | end 149 | 150 | def self.helptext 151 | "Usage: eyaml #{prettyname} [options]" 152 | end 153 | 154 | def self.execute 155 | raise StandardError, "This command is not implemented yet (#{to_s.split('::').last})" 156 | end 157 | 158 | def self.prettyname 159 | Utils.snakecase to_s.split('::').last 160 | end 161 | 162 | def self.hidden? 163 | false 164 | end 165 | end 166 | end 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/parser/encrypted_tokens.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/parser/token' 2 | require 'hiera/backend/eyaml/utils' 3 | require 'hiera/backend/eyaml/encryptor' 4 | require 'hiera/backend/eyaml' 5 | require 'base64' 6 | 7 | class Hiera 8 | module Backend 9 | module Eyaml 10 | module Parser 11 | class EncToken < Token 12 | @@tokens_map = {} 13 | @@encrypt_unchanged = true 14 | attr_reader :format, :cipher, :encryptor, :indentation, :plain_text, :id 15 | 16 | def self.encrypted_value(format, encryption_scheme, cipher, match, indentation = '') 17 | decryptor = Encryptor.find encryption_scheme 18 | plain_text = decryptor.decrypt(decryptor.decode(cipher)) 19 | EncToken.new(format, plain_text, decryptor, cipher, match, indentation) 20 | end 21 | 22 | def self.decrypted_value(format, plain_text, encryption_scheme, match, id, indentation = '') 23 | encryptor = Encryptor.find encryption_scheme 24 | cipher = encryptor.encode(encryptor.encrypt(plain_text)) 25 | id_number = id.nil? ? nil : id.gsub(/\(|\)/, '').to_i 26 | EncToken.new(format, plain_text, encryptor, cipher, match, indentation, id_number) 27 | end 28 | 29 | def self.plain_text_value(format, plain_text, encryption_scheme, match, id, indentation = '') 30 | encryptor = Encryptor.find encryption_scheme 31 | id_number = id.gsub(/\(|\)/, '').to_i unless id.nil? 32 | EncToken.new(format, plain_text, encryptor, '', match, indentation, id_number) 33 | end 34 | 35 | def self.tokens_map 36 | @@tokens_map 37 | end 38 | 39 | def self.set_encrypt_unchanged(encrypt_unchanged) 40 | @@encrypt_unchanged = encrypt_unchanged 41 | end 42 | 43 | def self.encrypt_unchanged 44 | @@encrypt_unchanged 45 | end 46 | 47 | def initialize(format, plain_text, encryptor, cipher, match = '', indentation = '', id = nil) 48 | @format = format 49 | @plain_text = Utils.convert_to_utf_8(plain_text) 50 | @encryptor = encryptor 51 | @cipher = cipher 52 | @indentation = indentation 53 | @id = id 54 | super(match) 55 | end 56 | 57 | def to_encrypted(args = {}) 58 | label = args[:label] 59 | label_string = label.nil? ? '' : "#{label}: " 60 | format = args[:format].nil? ? @format : args[:format] 61 | encryption_method = args[:change_encryption] 62 | unless encryption_method.nil? 63 | @encryptor = Encryptor.find encryption_method 64 | @cipher = Base64.strict_encode64(@encryptor.encrypt(@plain_text)) 65 | end 66 | case format 67 | when :block 68 | @cipher = @cipher.gsub(/\s/, '') 69 | chevron = (args[:use_chevron].nil? || args[:use_chevron]) ? ">\n" : '' 70 | "#{label_string}#{chevron}" + @indentation + "ENC[#{@encryptor.tag},#{@cipher}]".scan(/.{1,60}/).join("\n" + @indentation) 71 | when :string 72 | ciphertext = @cipher.gsub(/[\n\r]/, '') 73 | "#{label_string}ENC[#{@encryptor.tag},#{ciphertext}]" 74 | else 75 | raise "#{@format} is not a valid format" 76 | end 77 | end 78 | 79 | def to_decrypted(args = {}) 80 | label = args[:label] 81 | label_string = label.nil? ? '' : "#{label}: " 82 | format = args[:format].nil? ? @format : args[:format] 83 | index = args[:index].nil? ? '' : "(#{args[:index]})" 84 | EncToken.tokens_map[index] = @plain_text if @@encrypt_unchanged == false 85 | 86 | case format 87 | when :block 88 | chevron = (args[:use_chevron].nil? || args[:use_chevron]) ? ">\n" : '' 89 | "#{label_string}#{chevron}" + indentation + "DEC#{index}::#{@encryptor.tag}[" + @plain_text + ']!' 90 | when :string 91 | "#{label_string}DEC#{index}::#{@encryptor.tag}[" + @plain_text + ']!' 92 | else 93 | raise "#{@format} is not a valid format" 94 | end 95 | end 96 | 97 | def to_plain_text 98 | @plain_text 99 | end 100 | end 101 | 102 | class EncTokenType < TokenType 103 | def create_enc_token(match, type, enc_comma, cipher, indentation = '') 104 | encryption_scheme = enc_comma.nil? ? Eyaml.default_encryption_scheme : enc_comma.split(',').first 105 | EncToken.encrypted_value(type, encryption_scheme, cipher, match, indentation) 106 | end 107 | end 108 | 109 | class EncHieraTokenType < EncTokenType 110 | def initialize 111 | @regex = %r{ENC\[(\w+,)?([a-zA-Z0-9+/ =\n]+?)\]} 112 | @string_token_type = EncStringTokenType.new 113 | end 114 | 115 | def create_token(string) 116 | @string_token_type.create_token(string.gsub(/\s/, '')) 117 | end 118 | end 119 | 120 | class EncStringTokenType < EncTokenType 121 | def initialize 122 | @regex = %r{ENC\[(\w+,)?([a-zA-Z0-9+/=]+?)\]} 123 | end 124 | 125 | def create_token(string) 126 | md = @regex.match(string) 127 | create_enc_token(string, :string, md[1], md[2]) 128 | end 129 | end 130 | 131 | class EncBlockTokenType < EncTokenType 132 | def initialize 133 | @regex = %r{>\n(\s*)ENC\[(\w+,)?([a-zA-Z0-9+/=\s]+?)\]} 134 | end 135 | 136 | def create_token(string) 137 | md = @regex.match(string) 138 | create_enc_token(string, :block, md[2], md[3], md[1]) 139 | end 140 | end 141 | 142 | class DecStringTokenType < TokenType 143 | def initialize 144 | @regex = /DEC(\(\d+\))?::(\w+)\[(.+?)\]!/m 145 | end 146 | 147 | def create_token(string) 148 | md = @regex.match(string) 149 | return EncToken.plain_text_value(:string, md[3], md[2], string, md[1]) if EncToken.encrypt_unchanged == false && !md[1].nil? && (md[3] == EncToken.tokens_map[md[1]]) 150 | 151 | EncToken.decrypted_value(:string, md[3], md[2], string, md[1]) 152 | end 153 | end 154 | 155 | class DecBlockTokenType < TokenType 156 | def initialize 157 | @regex = />\n(\s*)DEC(\(\d+\))?::(\w+)\[(.+?)\]!/m 158 | end 159 | 160 | def create_token(string) 161 | md = @regex.match(string) 162 | return EncToken.plain_text_value(:string, md[4], md[3], string, md[2]) if EncToken.encrypt_unchanged == false && !md[2].nil? && (md[4] == EncToken.tokens_map[md[2]]) 163 | 164 | EncToken.decrypted_value(:block, md[4], md[3], string, md[2], md[1]) 165 | end 166 | end 167 | end 168 | end 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /lib/hiera/backend/eyaml/subcommands/edit.rb: -------------------------------------------------------------------------------- 1 | require 'hiera/backend/eyaml/edithelper' 2 | require 'hiera/backend/eyaml/highlinehelper' 3 | require 'hiera/backend/eyaml/options' 4 | require 'hiera/backend/eyaml/parser/parser' 5 | require 'hiera/backend/eyaml/subcommand' 6 | require 'hiera/backend/eyaml/parser/encrypted_tokens' 7 | 8 | class Hiera 9 | module Backend 10 | module Eyaml 11 | module Subcommands 12 | class Edit < Subcommand 13 | def self.options 14 | [{ name: :no_preamble, 15 | description: "Don't prefix edit sessions with the informative preamble", }, 16 | { name: :no_decrypt, 17 | short: '-d', 18 | description: 'Do not decrypt existing encrypted content. New content marked properly will be encrypted.', },] 19 | end 20 | 21 | def self.description 22 | 'edit an eyaml file' 23 | end 24 | 25 | def self.helptext 26 | 'Usage: eyaml edit [options] ' 27 | end 28 | 29 | def self.prefix 30 | '# |' 31 | end 32 | 33 | def self.preamble 34 | tags = (['pkcs7'] + Plugins.plugins.collect do |plugin| 35 | plugin.name.split('hiera-eyaml-').last 36 | end).collect { |name| Encryptor.find(name).tag } 37 | 38 | preamble = <<~EOS 39 | This is eyaml edit mode. This text (lines starting with #{prefix} at the top of 40 | the file) will be removed when you save and exit. 41 | - To edit encrypted values, change the content of the DEC()::PKCS7[]! 42 | block#{(tags.size > 1) ? " (or #{tags.drop(1).collect { |tag| "DEC()::#{tag}[]!" }.join(' or ')})." : '.'} 43 | WARNING: DO NOT change the number in the parentheses. 44 | - To add a new encrypted value copy and paste a new block from the 45 | appropriate example below. Note that: 46 | * the text to encrypt goes in the square brackets 47 | * ensure you include the exclamation mark when you copy and paste 48 | * you must not include a number when adding a new block 49 | e.g. #{tags.collect { |tag| "DEC::#{tag}[]!" }.join(' -or- ')} 50 | EOS 51 | 52 | preamble.gsub(/^/, "#{prefix} ") 53 | end 54 | 55 | def self.validate(options) 56 | Optimist.die 'You must specify an eyaml file' if ARGV.empty? 57 | options[:source] = :eyaml 58 | options[:eyaml] = ARGV.shift 59 | if File.exist? options[:eyaml] 60 | begin 61 | options[:input_data] = File.read options[:eyaml] 62 | rescue StandardError 63 | raise StandardError, "Could not open file for reading: #{options[:eyaml]}" 64 | end 65 | else 66 | LoggingHelper.info "#{options[:eyaml]} doesn't exist, editing new file" 67 | options[:input_data] = '---' 68 | end 69 | options 70 | end 71 | 72 | def self.execute 73 | editor = EditHelper.find_editor 74 | 75 | Parser::EncToken.set_encrypt_unchanged(false) 76 | 77 | # The 'no_' option has special handling - bypass that and just check if a flag was set. 78 | if Eyaml::Options[:no_decrypt_given] 79 | decrypted_input = Eyaml::Options[:input_data] 80 | decrypted_file_content = Eyaml::Options[:no_preamble] ? decrypted_input : (preamble + decrypted_input) 81 | else 82 | encrypted_parser = Parser::ParserFactory.encrypted_parser 83 | tokens = encrypted_parser.parse Eyaml::Options[:input_data] 84 | decrypted_input = tokens.each_with_index.to_a.map { |(t, index)| t.to_decrypted index: index }.join 85 | decrypted_file_content = Eyaml::Options[:no_preamble] ? decrypted_input : (preamble + decrypted_input) 86 | end 87 | 88 | begin 89 | decrypted_file ||= EditHelper.write_tempfile decrypted_file_content 90 | system "#{editor} \"#{decrypted_file}\"" 91 | status = $? 92 | 93 | raise StandardError, 'File was moved by editor' unless File.file? decrypted_file 94 | 95 | raw_edited_file = File.read decrypted_file 96 | # strip comments at start of file 97 | edited_file = raw_edited_file.split($/, -1).drop_while { |line| line.start_with?(prefix) }.join($/) 98 | 99 | raise StandardError, "Editor #{editor} has not exited?" unless status.exited? 100 | 101 | unless status.exitstatus == 0 102 | raise StandardError, 103 | "Editor did not exit successfully (exit code #{status.exitstatus}), aborting" 104 | end 105 | raise StandardError, 'Edited file is blank' if edited_file.empty? 106 | 107 | if edited_file == decrypted_input 108 | LoggingHelper.info 'No changes detected, exiting' 109 | else 110 | decrypted_parser = Parser::ParserFactory.decrypted_parser 111 | edited_tokens = decrypted_parser.parse(edited_file) 112 | 113 | # check that the tokens haven't been copy / pasted 114 | used_ids = edited_tokens.find_all { |t| t.class.name =~ /::EncToken$/ and !t.id.nil? }.map { |t| t.id } 115 | if used_ids.length != used_ids.uniq.length 116 | raise RecoverableError, 117 | "A duplicate DEC(ID) was found so I don't know how to proceed. This is probably because you copy and pasted a value - if you do this please delete the ID and parentheses" 118 | end 119 | 120 | # replace untouched values with the source values 121 | edited_denoised_tokens = edited_tokens.map do |token| 122 | if token.class.name =~ /::EncToken$/ && !token.id.nil? 123 | old_token = tokens[token.id] 124 | if old_token.plain_text.eql? token.plain_text 125 | old_token 126 | else 127 | token 128 | end 129 | else 130 | token 131 | end 132 | end 133 | 134 | encrypted_output = edited_denoised_tokens.map { |t| t.to_encrypted }.join 135 | 136 | filename = Eyaml::Options[:eyaml] 137 | File.write("#{filename}", encrypted_output) 138 | end 139 | rescue RecoverableError => e 140 | LoggingHelper.info e 141 | raise e unless agree 'Return to the editor to try again?' 142 | 143 | retry 144 | ensure 145 | EditHelper.secure_file_delete file: decrypted_file, 146 | num_bytes: [edited_file.length, 147 | decrypted_input.length,].max 148 | end 149 | 150 | nil 151 | end 152 | end 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /sublime_text/eyaml.syntax_definition.json: -------------------------------------------------------------------------------- 1 | { "name": "EYAML", 2 | "scopeName": "source.eyaml", 3 | "fileTypes": ["eyaml"], 4 | "foldingStartMarker": "^[^#]\\s*.*:(\\s*\\[?| &.+)?$", 5 | "foldingStopMarker": "^\\s*$|^\\s*\\}|^\\s*\\]|^\\s*\\)", 6 | "keyEquivalent": "^~Y", 7 | "repository": { 8 | "erb": { 9 | "end": "%>", 10 | "begin": "<%+(?!>)=?", 11 | "patterns": [ 12 | { 13 | "match": "(#).*?(?=%>)", 14 | "captures": { 15 | "1": { 16 | "name": "punctuation.definition.comment.ruby" 17 | } 18 | }, 19 | "name": "comment.line.number-sign.ruby" 20 | }, 21 | { 22 | "include": "source.ruby.rails" 23 | } 24 | ], 25 | "captures": { 26 | "0": { 27 | "name": "punctuation.section.embedded.ruby" 28 | } 29 | }, 30 | "name": "source.ruby.rails.embedded.html" 31 | }, 32 | "escaped_char": { 33 | "match": "\\\\.", 34 | "name": "constant.character.escape.yaml" 35 | } 36 | }, 37 | "patterns": [ 38 | { 39 | "include": "#erb" 40 | }, 41 | { 42 | "end": "^(?!^\\1)|^(?=\\1(-|\\w+\\s*:)|#)", 43 | "begin": "^(\\s*)(?:(-)|(?:(-\\s*)?(\\w+\\s*(:))))\\s*(\\||>)", 44 | "beginCaptures": { 45 | "3": { 46 | "name": "punctuation.definition.entry.yaml" 47 | }, 48 | "4": { 49 | "name": "entity.name.tag.yaml" 50 | }, 51 | "5": { 52 | "name": "punctuation.separator.key-value.yaml" 53 | }, 54 | "2": { 55 | "name": "punctuation.definition.entry.yaml" 56 | } 57 | }, 58 | "patterns": [ 59 | { 60 | "include": "#erb" 61 | } 62 | ], 63 | "name": "string.unquoted.block.yaml" 64 | }, 65 | { 66 | "match": "(?:(?:(-\\s*)?(\\w+\\s*(:)))|(-))\\s*((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f)?\\s*$", 67 | "captures": { 68 | "3": { 69 | "name": "punctuation.separator.key-value.yaml" 70 | }, 71 | "4": { 72 | "name": "punctuation.definition.entry.yaml" 73 | }, 74 | "1": { 75 | "name": "punctuation.definition.entry.yaml" 76 | }, 77 | "2": { 78 | "name": "entity.name.tag.yaml" 79 | } 80 | }, 81 | "name": "constant.numeric.yaml" 82 | }, 83 | { 84 | "match": "(?:(?:(-\\s*)?(\\w+\\s*(:)))|(-))\\s*(?:((\")[^\"]*(\"))|((')[^']*('))|([^,{}&#\\[\\]]+))\\s*", 85 | "captures": { 86 | "7": { 87 | "name": "punctuation.definition.string.end.yaml" 88 | }, 89 | "3": { 90 | "name": "punctuation.separator.key-value.yaml" 91 | }, 92 | "11": { 93 | "name": "string.unquoted.yaml" 94 | }, 95 | "4": { 96 | "name": "punctuation.definition.entry.yaml" 97 | }, 98 | "8": { 99 | "name": "string.quoted.single.yaml" 100 | }, 101 | "9": { 102 | "name": "punctuation.definition.string.begin.yaml" 103 | }, 104 | "5": { 105 | "name": "string.quoted.double.yaml" 106 | }, 107 | "1": { 108 | "name": "punctuation.definition.entry.yaml" 109 | }, 110 | "6": { 111 | "name": "punctuation.definition.string.begin.yaml" 112 | }, 113 | "10": { 114 | "name": "punctuation.definition.string.end.yaml" 115 | }, 116 | "2": { 117 | "name": "entity.name.tag.yaml" 118 | } 119 | }, 120 | "name": "string.unquoted.yaml" 121 | }, 122 | { 123 | "match": "(?:(?:(-\\s*)?(\\w+\\s*(:)))|(-))\\s*([0-9]{4}-[0-9]{2}-[0-9]{2})\\s*$", 124 | "captures": { 125 | "3": { 126 | "name": "punctuation.separator.key-value.yaml" 127 | }, 128 | "4": { 129 | "name": "punctuation.definition.entry.yaml" 130 | }, 131 | "1": { 132 | "name": "punctuation.definition.entry.yaml" 133 | }, 134 | "2": { 135 | "name": "entity.name.tag.yaml" 136 | } 137 | }, 138 | "name": "constant.other.date.yaml" 139 | }, 140 | { 141 | "match": "(\\w.*?)(:)\\s*((\\!\\!)omap)?", 142 | "captures": { 143 | "3": { 144 | "name": "keyword.other.omap.yaml" 145 | }, 146 | "4": { 147 | "name": "punctuation.definition.keyword.yaml" 148 | }, 149 | "1": { 150 | "name": "entity.name.tag.yaml" 151 | }, 152 | "2": { 153 | "name": "punctuation.separator.key-value.yaml" 154 | } 155 | }, 156 | "name": "meta.tag.yaml" 157 | }, 158 | { 159 | "match": "(\\&|\\*)\\w.*?$", 160 | "captures": { 161 | "1": { 162 | "name": "punctuation.definition.variable.yaml" 163 | } 164 | }, 165 | "name": "variable.other.yaml" 166 | }, 167 | { 168 | "end": "\"", 169 | "begin": "\"", 170 | "beginCaptures": { 171 | "0": { 172 | "name": "punctuation.definition.string.begin.yaml" 173 | } 174 | }, 175 | "patterns": [ 176 | { 177 | "include": "#escaped_char" 178 | }, 179 | { 180 | "include": "#erb" 181 | } 182 | ], 183 | "endCaptures": { 184 | "0": { 185 | "name": "punctuation.definition.string.end.yaml" 186 | } 187 | }, 188 | "name": "string.quoted.double.yaml" 189 | }, 190 | { 191 | "end": "'", 192 | "begin": "'", 193 | "beginCaptures": { 194 | "0": { 195 | "name": "punctuation.definition.string.begin.yaml" 196 | } 197 | }, 198 | "patterns": [ 199 | { 200 | "include": "#escaped_char" 201 | }, 202 | { 203 | "include": "#erb" 204 | } 205 | ], 206 | "endCaptures": { 207 | "0": { 208 | "name": "punctuation.definition.string.end.yaml" 209 | } 210 | }, 211 | "name": "string.quoted.single.yaml" 212 | }, 213 | { 214 | "end": "`", 215 | "begin": "`", 216 | "beginCaptures": { 217 | "0": { 218 | "name": "punctuation.definition.string.begin.yaml" 219 | } 220 | }, 221 | "patterns": [ 222 | { 223 | "include": "#escaped_char" 224 | }, 225 | { 226 | "include": "#erb" 227 | } 228 | ], 229 | "endCaptures": { 230 | "0": { 231 | "name": "punctuation.definition.string.end.yaml" 232 | } 233 | }, 234 | "name": "string.interpolated.yaml" 235 | }, 236 | { 237 | "match": "(\\<\\<): ((\\*).*)$", 238 | "captures": { 239 | "3": { 240 | "name": "punctuation.definition.keyword.yaml" 241 | }, 242 | "1": { 243 | "name": "entity.name.tag.yaml" 244 | }, 245 | "2": { 246 | "name": "keyword.operator.merge-key.yaml" 247 | } 248 | }, 249 | "name": "keyword.operator.merge-key.yaml" 250 | }, 251 | { 252 | "match": "( |\t)+$", 253 | "disabled": "1", 254 | "name": "invalid.deprecated.trailing-whitespace.yaml" 255 | }, 256 | { 257 | "match": "(? 6 | once upon a time 7 | in a galaxy far far 8 | away 9 | 10 | encrypted_block: > 11 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 12 | DQYJKoZIhvcNAQEBBQAEggEAYzeWn3MBLhOs4hokxMCWcDd9VuwCylQRUQ0w 13 | KwCObeORw8PJkCDvi5ZIA2YkrvYTT6u3/7KfAiHd0Rg1WLb9et0Mg/Fd3DFF 14 | 7qhqOGHoQt3+4eKzlcikeR0/Lqrq2vTpqZ2Sw1CZ7Dn+Z4ll95p7lp97rb2J 15 | kYTVroLYGWEcsS3JZLL4/l3z0bJbXNKKqJ1aHCAFq+wmWXeb6cDvvyHFg2N/ 16 | vGPFEQjP7AbWhxHxXDbYIGcU073u5NtE40JXL8SH82iHxqRF8s9g6Dh5cmjg 17 | AY2pkBD9e6N78NNx+PAJswsFAV4DOCbXdf2BisyYbM3na35MVfyb6ggDegrE 18 | ebOxxDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWeGlYS5cQoX78L6LK 19 | /mczgCD/pI7usp1XPebnN8CngxHXuUjj5S+6IUpOW6l2JgUeWw==] 20 | 21 | encrypted_tabbed_block: > 22 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 23 | DQYJKoZIhvcNAQEBBQAEggEAYzeWn3MBLhOs4hokxMCWcDd9VuwCylQRUQ0w 24 | KwCObeORw8PJkCDvi5ZIA2YkrvYTT6u3/7KfAiHd0Rg1WLb9et0Mg/Fd3DFF 25 | 7qhqOGHoQt3+4eKzlcikeR0/Lqrq2vTpqZ2Sw1CZ7Dn+Z4ll95p7lp97rb2J 26 | kYTVroLYGWEcsS3JZLL4/l3z0bJbXNKKqJ1aHCAFq+wmWXeb6cDvvyHFg2N/ 27 | vGPFEQjP7AbWhxHxXDbYIGcU073u5NtE40JXL8SH82iHxqRF8s9g6Dh5cmjg 28 | AY2pkBD9e6N78NNx+PAJswsFAV4DOCbXdf2BisyYbM3na35MVfyb6ggDegrE 29 | ebOxxDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWeGlYS5cQoX78L6LK 30 | /mczgCD/pI7usp1XPebnN8CngxHXuUjj5S+6IUpOW6l2JgUeWw==] 31 | 32 | encrypted_default_encryption_block: > 33 | ENC[MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 34 | DQYJKoZIhvcNAQEBBQAEggEAYzeWn3MBLhOs4hokxMCWcDd9VuwCylQRUQ0w 35 | KwCObeORw8PJkCDvi5ZIA2YkrvYTT6u3/7KfAiHd0Rg1WLb9et0Mg/Fd3DFF 36 | 7qhqOGHoQt3+4eKzlcikeR0/Lqrq2vTpqZ2Sw1CZ7Dn+Z4ll95p7lp97rb2J 37 | kYTVroLYGWEcsS3JZLL4/l3z0bJbXNKKqJ1aHCAFq+wmWXeb6cDvvyHFg2N/ 38 | vGPFEQjP7AbWhxHxXDbYIGcU073u5NtE40JXL8SH82iHxqRF8s9g6Dh5cmjg 39 | AY2pkBD9e6N78NNx+PAJswsFAV4DOCbXdf2BisyYbM3na35MVfyb6ggDegrE 40 | ebOxxDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWeGlYS5cQoX78L6LK 41 | /mczgCD/pI7usp1XPebnN8CngxHXuUjj5S+6IUpOW6l2JgUeWw==] 42 | 43 | simple_array: 44 | - array1 45 | - array2 46 | - array3 47 | 48 | encrypted_array_with_strings: 49 | - ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAPBII8uG/2up91TFBV4Ro5h7qkSlREXyiABQtIjfmR+hwE5v12rxSjFrNRqLJFDzTp9kOHZRpbrN7oGwj2L+qQx26PBF5sTcSN+iXr4DIhmOcSnwK+FDUi1l13zVESobUtk++tp44NR5R/ssy7Da476aBCzK1IW3DjZN8dLGLZRvF9I6e3Sg1r0SB/x+1dKpOsqHXcA6rXT75ww1OOh8+/TPMnRIXBbdd+6TLUuom065JTkfgRmzEXfJFZOMu4H2g0eUjNUJBHhEE7cMyWVOap0vUmMrjc2oLIuJSxwZ15JFH4bGOc9U6fbm5t7VOMtPtH1aVvA54hCqIbf7c9cbPwDA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBExu0Pqe4YHDVeMYKOpYcNgBBplVycTULp98dIuHc04iGM] 50 | - ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAoEOorG1Vd8gSQVmzKaUyH1xuxA8VtFsOfrN9lzba1DxrySAH4e28N3fUwcwGR37XIlmxU9tDXDuvGom5dV+ajSBgmaVoUS3PerPUOxjC3lQ7Q2J9+Af51hfFlGovNZz8p/eelQoClZk93p9+Tn4VM84sGPXZiOMi1hDQjU5kOtFnfendNQmEL/8T0/nns5MtCvleuYkPEfw5COnsBmjkRBC2ancBd88leG1DCsj32XNWFBG2jZoJbIsczIBNOduP1ajLMXrq7EVUOk5DCZQB1Ku1dWGPN55GzWe1GxxVIpFUzTsKAExafM+FYcamKFACeL9yFtei00mm6kyc1+GRuzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWbEJkf7PwSwCpbMUm68mZgCCMMj4zrPZVmzj5PEx3ElW0TAdAxNHmN7DPpbBkSLcKcQ==] 51 | 52 | mixed_array_with_strings: 53 | - array4 54 | - ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAeHR6sUJ/dBEo3ua7GEulLaxVwg029zo68XjsZQiTxg2I3UzH/nE+XJYGx9jRYUO7dehvMjam+LQ3ILFnaX/oIExreb1dXv7xpEyYqQAldpZdflyzAANmSxuzmxjyXQPWezyNTWta/Fn2BTf4pv++CU+Vps9g4idZ7/a3619Vp6lEZGycmSIirkE74PFcrVWlWQXqDW4VMkBQ1jEyu0mUnt+D0cZypNPwNTXnxzcAYYKbvfv2piE+dk4HCYt3txcdCc59Gyc035JsVv6fH7ksotM43+mgK4zOzaOpYg6RZIvSCt1vXNSwsVUIOEjdqe9VqNzt4W3jrXF1Ot16slorszA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAabuHst7lWfK4lClC74O2PgBAgOeDR0fJBpE63mqXFyYLk] 55 | - array5 56 | 57 | encrypted_array_with_blocks: 58 | - > 59 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 60 | DQYJKoZIhvcNAQEBBQAEggEAQjttHzRiVQWu2Vy7zgWQidZwBaMcXozdrZ+m 61 | ICa7B+v9dOawM/K0+nUY7/BpC8EyxnRBd4RC5tUTDOtMjDMUuHuYk5XuT8lg 62 | c8GjuTkwqwem/DqjO7SlfI54fDInrxhnB3OBEnszrpg9opcU/7GiLtc3OZWP 63 | 9Asjc1NFXU1c+epppC23Mnhj20IWwtWjL2eiJ//awTIYAiZuLAz67DGHtXXg 64 | 9AyLmam1zu3wSQ5oBaIlsCD6uURAvQpPbFLpighDoNpmrPEHxx2ic3CRZa1z 65 | T6aFt73F2zvfHqAW5DgWw/iOw7O9pQB/XVx5PllayL3o0Qvm0zg0eXwEaUwq 66 | aV5tSzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAK6XVBBwkRexVRrPN7 67 | 1AbggCDFkRxpgVv6m24BcGZip0Fl7pYI6aP6LQrPU20ZmGApzw==] 68 | - > 69 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 70 | DQYJKoZIhvcNAQEBBQAEggEALr1EHJG+jshOtmsxWVdnu/rtJuWiz3f6sO0F 71 | x4or64faJpt6IvOMrY1VLMl72QNVGW5EZueh0uM9vgfhOXCMV42LGO6O0qXM 72 | AmYyVL/07SomaXDuOTFuufdyScOYIj4xUxXPSIQPNZ3bef92K9UPh8HADXY9 73 | 2kxWVF2KQAkyzHpz0c0cuQjButz77oAYbyzN4l/l/o0X5teTrU5Ghly4i1c/ 74 | 6KMn0eFZKY/midRePg8xXkkVCzlyS83dfmJOHboe7cw8t21q40od0+1H6AwF 75 | WqhGAYW4qlQmj/x9ymXv9dxj8kYOmtL4x9eefZppCfwMmk6gSKnURTBe/Do2 76 | QLWU3jBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBUFtWWAaIlqDWgOofQ 77 | /jpLgCDXTSezJvwONiokkl3MHryu/xsAlMGLyd4fDUC1XPQsRA==] 78 | 79 | mixed_array_with_blocks: 80 | - > 81 | tomorrow and tomorrow and 82 | tomorrow creeps in this petty 83 | pace 84 | - > 85 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 86 | DQYJKoZIhvcNAQEBBQAEggEAj9R0rZ98jseASAthfPUflx1oj2bNmOoCkfv9 87 | H9PpsQnr4NzqrTpYLAX72sewLD2GZYAA61ukb8Z6KRjNg6iSTZjYuRA7jhRc 88 | QV6NJ3KgYI7w4RJs4lUvYOIgZe6MwXjPDeoEaSTDDaee5pld5AT//MNXtPgp 89 | Ez2ms9Ude+PvGbf7y38HKhkguSbDs34aacuz4X7IGEZCZMrbfxUo1M+HzPlY 90 | RvlmjDHwWJmG9Aj1G439AKUwr7KJgTvPzzZLcXz/lENdjk92HLSEaRFMMnTJ 91 | XkJLIXcBdzdd6gw+3WqEljQUYAmOv36avTZt7IA1AeCycTa+xDCKZ4gZE/O7 92 | zasLkTBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAI+wtFyu4rvbeY5Zwg 93 | VR0ggCDK2aXbmJOBwtj6GyRBYokOEk8EcLuUTDh+GOvYaErUdQ==] 94 | - > 95 | when shall we three meet again 96 | in thunder lightning or 97 | in rain 98 | 99 | mixed_array_with_random: 100 | - ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEApxf3lqPCoidIFp5qvB21dUhlmCHx6JLm7rtt9m6rXwc35VrJVukSCIrjvAz1b/osEU+RdSEQh9WL2E65QHB9aHklWdu4SgR3QvC/TU4uVWQCvfy3g/bpX3D/j13BkSpe99nH0Va0kZpCnaorHEwiv9fvs5VneGIwSKiBIy/5lYdcmmHWPSlE+4G285XwznMKUqh+P/prUTn664oDA8LeRGitENfndmFGLPaoEbgfJLAQQlVPzt/DhR/YiYoG5qHh9445NAVo/MVq13SVzbxCwyg7WVrNTI3KK4Z2/hDiizRHpE/L7ceWfJiXCUQ4ouy2QvWNcNjmGTM0bRZDgep8XDBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBWzWT0Fn9sVdKPp0BoGWIcgCC0ga55Q3kw9i+D6acq4TOlI1rcdIyS/lwBSe7gixWQdg==] 101 | - > 102 | ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAw 103 | DQYJKoZIhvcNAQEBBQAEggEAZIrpP5Gtt54I4S68hrZj3pjaqO9IcEnX0J66 104 | I7Zc8+r9VUsahJWSGlDutvmz5b8vo94ZqWy9vKaJFKBc762ih8YDhMKjDTKL 105 | 2Pbdshv/cbWF09oF2HoNqU+qC8OK+Uku+lf7F8UOmkzTsB2QS6LzQKXoZtff 106 | WWum8ZNfShgCHsb6qy+1C/pNPNMSvO+JnFhTp7e7MS62wIAY6denn2ODCj4c 107 | MJhU8siBOWcyIBOHDdlHr658qXfpixN3vmN4qpXoqD5A4r2rv5Jh0uqYNYdo 108 | peROsTg5yYz46NovQF/iM4ZOed+jcHjANFW1+tLsT8MgesvN19pTpLyVeCdq 109 | lwed8jBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDs+GHGdE7N5Livn7K+ 110 | Dfs1gCBnNYMzne3ae3pyxKTf2FUaJyZWOgmCPMoWAwgr20Gkvw==] 111 | - > 112 | i wondered lonely 113 | as a cloud 114 | - the grand old duke of york 115 | 116 | simple_hash: 117 | key1: value1 118 | key2: value2 119 | 120 | encrypted_hash_value: 121 | key5: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAfXcVAj7iddi1siO4eT3ZbJF6C3z+5iHOd8RXf/AFRV6ocQP3G70ABUcWpjp289Rc968/T1vHgsXjaUcyOl43XcjOdtizyYrNUkFGG02VPjeAa7rqSIAy9Ot58PGEFC+/gfyvFXCr7mQoP6QUE0qQbxsRt0icUhg0HXXFFtYlddJDEovdWy3KVjx7Dl0AxYp8zbEnXgTmUdfezl3P89fWce9wEq9AFNsFzbQKA25XTTt77xyGJvtZSOJSy4mB65e5VX0rb09kL93ofpgmra/0D3x13Afi5YJHtOSzWkHNoBwcvAXsigRrQcvFrgVDQhOK8J6wTdlQr/XhwDVolq5/zjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAvmHWbpyACAaHkpsP1tHvpgBCFqQ4ZpLC5gagc8i9Eia74] 122 | key6: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAYXaRRYmZJUtU5zo/yVVVknavcsEouVPbs8gXwRXbVK+anEOyWNaFalbA2n16dIhD9kIVbfJ3MiBnUu3m/CbvyTrt4lVpw5r8TRuxjXh8F8C39k9bZbKOgj+zj8OCMfZYgRR3BTXN5fym6BLFQ1oWIaW9GxMc3671OyymudJE230eT1dk8ivx95giFAHQRasAyBVsf0YRuRmH5GVnOiD9zmTNb0eKTcxdGXdVTgc2rGbOlFhQLOS9CHcEryulAtbKH22foUTnYWugFk8SrRpdyS3HViimnSDOfx1ulRPa9TktXaQwR3YED/z4gbQ0x5Gx30tm0lmTYFCSt2FDFd54eTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDy/ITSXoe+hXa6ewQlAAefgBCd16Ly4Z9nsitcdMS2fVvz] 123 | 124 | multi_encryption: ENC[PLAINTEXT,amFtbXk=] ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAKjKPUtDSN+X/n91bpIjhZHW3eLG9efNNWz7unq8ToxQOIMLrQcr3mj+83E1UNhmRu4TSymSz0kFkkPtjWd2TAkAG3PZvUj6ldFeUhbPMAvTOH/Q23hEZfQdI0GmZHTe6YsYBjJwAxEegXfdg1HKzCY3SBZoMTCEu9yFIJKd3slPxw8zPI6GERTg2ZBRWIckv9W9t7EN7nu9liWR0csRPEYwXUiAZlrgdX2r2Fhx/ZzWZ0tWJ55BiYYzE9U9bvSGZOjZJpWU9wUaOhpAhAx9xaIg48CO5THXMvV+8ss/zEMhYHGfhN56Cr69oQRDXGunYKnWT9tgoDCrxMGIrurtBnDA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBntvZSBPjinBRMofJhVxEtgBAd3fhzETLtxJm58bnlM3ci] 125 | -------------------------------------------------------------------------------- /features/edit.feature: -------------------------------------------------------------------------------- 1 | Feature: eyaml editing 2 | 3 | In order to edit encrypted data 4 | As a developer using hiera-eyaml 5 | I want to use the eyaml tool to edit data in various ways 6 | 7 | Scenario: decrypt an eyaml file 8 | Given my EDITOR is set to "/bin/cat" 9 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 10 | When I run `eyaml edit test_input.eyaml` 11 | Then the output should match /encrypted_string: DEC\(\d+\)::PKCS7\[planet of the apes\]\!/ 12 | And the output should match /encrypted_default_encryption_string: DEC\(\d+\)::PKCS7\[planet of the apes\]\!/ 13 | And the output should match /encrypted_block: >\n\s+DEC\(\d+\)::PKCS7\[gangs of new york\]\!/ 14 | And the output should match /encrypted_tabbed_block: >\n\s+DEC\(\d+\)::PKCS7\[gangs of new york\]\!/ 15 | And the output should match /encrypted_default_encryption_block: >\n\s+DEC\(\d\)::PKCS7\[gangs of new york\]\!/ 16 | And the output should match /\- DEC\(\d+\)::PKCS7\[apocalypse now\]\!/ 17 | And the output should match /\- DEC\(\d+\)::PKCS7\[the count of monte cristo\]\!/ 18 | And the output should match /\- array4/ 19 | And the output should match /\- DEC\(\d+\)::PKCS7\[dr strangelove\]\!/ 20 | And the output should match /\- array5/ 21 | And the output should match /\- >\n\s+DEC\(\d+\)::PKCS7\[kramer vs kramer\]\!/ 22 | And the output should match /\- >\n\s+DEC\(\d+\)::PKCS7\[the manchurian candidate\]\!/ 23 | And the output should match /\- >\n\s+tomorrow and tomorrow and\s*\n\s+tomorrow creeps/ 24 | And the output should match /\- >\n\s+DEC\(\d+\)::PKCS7\[much ado about nothing\]\!/ 25 | And the output should match /\- >\n\s+when shall we three meet again\n\s+in thunder/ 26 | And the output should match /\- DEC\(\d+\)::PKCS7\[the english patient\]\!/ 27 | And the output should match /\- >\n\s+DEC\(\d+\)::PKCS7\[the pink panther\]\!/ 28 | And the output should match /\- >\n\s+i wondered lonely\s*\n\s+as a cloud/ 29 | And the output should match /\s+key5: DEC\(\d+\)::PKCS7\[value5\]\!/ 30 | And the output should match /\s+key6: DEC\(\d+\)::PKCS7\[value6\]\!/ 31 | And the output should match /multi_encryption: DEC\(\d+\)::PLAINTEXT\[jammy\]\! DEC\(\d+\)::PKCS7\[dodger\]!/ 32 | 33 | Scenario: decrypting a eyaml file should create a temporary file 34 | Given my EDITOR is set to "/usr/bin/env true" 35 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 36 | When I run `eyaml edit -v test_input.eyaml` 37 | Then the stderr should contain "Wrote temporary file" 38 | 39 | Scenario: decrypting a eyaml file should add a preamble 40 | Given my EDITOR is set to "/bin/cat" 41 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 42 | When I run `eyaml edit test_input.eyaml` 43 | Then the output should match /#| This is eyaml edit mode/ 44 | 45 | Scenario: decrypting a eyaml file with --no-preamble should NOT add a preamble 46 | Given my EDITOR is set to "/bin/cat" 47 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 48 | When I run `eyaml edit --no-preamble test_input.eyaml` 49 | Then the output should not match /#| This is eyaml edit mode/ 50 | 51 | Scenario: editing a eyaml file should not leave the preamble 52 | Given my EDITOR is set to "./convert_decrypted_values_to_uppercase.sh" 53 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 54 | When I run `eyaml edit test_input.eyaml` 55 | Then the file "test_input.eyaml" should not match /#| This is eyaml edit mode/ 56 | 57 | Scenario: editing a non-existant eyaml file should give you a blank file 58 | Given my EDITOR is set to "/bin/cat" 59 | When I run `bash -c 'rm non-existant-file.eyaml'` 60 | When I run `eyaml edit --no-preamble non-existant-file.eyaml` 61 | Then the output should match /^---/ 62 | 63 | Scenario: editing a non-existant eyaml file should save a new file 64 | Given my EDITOR is set to "./append.sh test_new_values.yaml" 65 | When I run `bash -c 'rm non-existant-file.eyaml'` 66 | When I run `eyaml edit non-existant-file.eyaml` 67 | When I run `eyaml decrypt -e non-existant-file.eyaml` 68 | Then the output should not match /#| This is eyaml edit mode/ 69 | And the output should match /new_key1: DEC::PKCS7\[new value one\]\!/ 70 | And the output should match /new_key2: DEC::PKCS7\[new value two\]\!/ 71 | 72 | Scenario: decrypt and reencrypt an eyaml file 73 | Given my EDITOR is set to "./convert_decrypted_values_to_uppercase.sh" 74 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 75 | When I run `eyaml edit test_input.eyaml` 76 | When I run `eyaml decrypt -e test_input.eyaml` 77 | Then the output should match /encrypted_string: DEC::PKCS7\[PLANET OF THE APES\]\!/ 78 | And the output should match /encrypted_block: >\n\s+DEC::PKCS7\[GANGS OF NEW YORK\]\!/ 79 | And the output should match /\- DEC::PKCS7\[APOCALYPSE NOW\]\!/ 80 | And the output should match /\- DEC::PKCS7\[THE COUNT OF MONTE CRISTO\]\!/ 81 | And the output should match /\- array4/ 82 | And the output should match /\- DEC::PKCS7\[DR STRANGELOVE\]\!/ 83 | And the output should match /\- array5/ 84 | And the output should match /\- >\n\s+DEC::PKCS7\[KRAMER VS KRAMER\]\!/ 85 | And the output should match /\- >\n\s+DEC::PKCS7\[THE MANCHURIAN CANDIDATE\]\!/ 86 | And the output should match /\- >\n\s+tomorrow and tomorrow and\s*\n\s+tomorrow creeps/ 87 | And the output should match /\- >\n\s+DEC::PKCS7\[MUCH ADO ABOUT NOTHING\]\!/ 88 | And the output should match /\- >\n\s+when shall we three meet again\n\s+in thunder/ 89 | And the output should match /\- DEC::PKCS7\[THE ENGLISH PATIENT\]\!/ 90 | And the output should match /\- >\n\s+DEC::PKCS7\[THE PINK PANTHER\]\!/ 91 | And the output should match /\- >\n\s+i wondered lonely\s*\n\s+as a cloud/ 92 | And the output should match /\s+key5: DEC::PKCS7\[VALUE5\]\!/ 93 | And the output should match /\s+key6: DEC::PKCS7\[VALUE6\]\!/ 94 | And the output should match /multi_encryption: DEC::PLAINTEXT\[JAMMY\]\! DEC::PKCS7\[DODGER\]\!/ 95 | 96 | Scenario: decrypt and reencrypt an eyaml file with multiple new values 97 | Given my EDITOR is set to "./append.sh test_new_values.yaml" 98 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 99 | When I run `eyaml edit test_input.eyaml` 100 | When I run `eyaml decrypt -e test_input.eyaml` 101 | Then the output should match /encrypted_string: DEC::PKCS7\[planet of the apes\]\!/ 102 | And the output should match /new_key1: DEC::PKCS7\[new value one\]\!/ 103 | And the output should match /new_key2: DEC::PKCS7\[new value two\]\!/ 104 | And the output should match /multi_encryption: DEC::PLAINTEXT\[jammy\]\! DEC::PKCS7\[dodger\]!/ 105 | 106 | Scenario: not editing a file should result in an untouched file 107 | Given my EDITOR is set to "/usr/bin/env true" 108 | When I run `bash -c 'cp test_edit.yaml test_edit.eyaml'` 109 | When I run `eyaml edit test_edit.eyaml` 110 | When I run `bash -c 'diff test_edit.yaml test_edit.eyaml'` 111 | Then the exit status should be 0 112 | 113 | Scenario: not editing a file should result in a no changes detected message 114 | Given my EDITOR is set to "/usr/bin/env true" 115 | When I run `bash -c 'cp test_edit.yaml test_edit.eyaml'` 116 | When I run `eyaml edit test_edit.eyaml` 117 | Then the stderr should contain "No changes detected" 118 | 119 | Scenario: not modifying the plaintext should result in no encryption 120 | Given my EDITOR is set to "sed -i.bak s/simple_array/test_array/g" 121 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 122 | When I run `eyaml edit -t test_input.eyaml` 123 | Then the output should not contain "PKCS7 encrypt" 124 | 125 | Scenario: modifying the plaintext should result in an encryption 126 | Given my EDITOR is set to "sed -i.bak s/value6/value7/g" 127 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 128 | When I run `eyaml edit -t test_input.eyaml` 129 | Then the output should contain "PKCS7 encrypt" 130 | 131 | Scenario: editing but not modifying a eyaml file with --no-preamble should be detected 132 | Given my EDITOR is set to "/usr/bin/env true" 133 | When I run `bash -c 'cp test_edit.yaml test_edit.eyaml'` 134 | When I run `eyaml edit --no-preamble test_edit.eyaml` 135 | Then the stderr should contain "No changes detected" 136 | 137 | Scenario: no-decrypt mode should not decrypt input 138 | Given my EDITOR is set to "/bin/cat" 139 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 140 | When I run `eyaml edit --no-decrypt test_input.eyaml` 141 | Then the output should not match /DEC\(\d+\)/ 142 | And the output should match /encrypted_string: ENC\[PKCS7,[^\]]+\]/ 143 | 144 | Scenario: no-decrypt mode should encrypt new values 145 | Given my EDITOR is set to "./append.sh test_new_values.yaml" 146 | When I run `bash -c 'cp test_edit.yaml test_edit.eyaml'` 147 | When I run `eyaml edit -d test_edit.eyaml` 148 | When I run `eyaml decrypt -e test_edit.eyaml` 149 | Then the output should match /new_key1: DEC::PKCS7\[new value one\]\!/ 150 | And the output should match /new_key2: DEC::PKCS7\[new value two\]\!/ 151 | 152 | Scenario: no-decrypt mode should not modify existing values 153 | Given my EDITOR is set to "./append.sh test_new_values.yaml" 154 | When I run `bash -c 'cp test_edit.yaml test_edit.eyaml'` 155 | When I run `eyaml edit -d test_edit.eyaml` 156 | When I run `cat test_edit.eyaml` 157 | Then the output should contain "encrypted_string: ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQAwDQYJKoZIhvcNAQEBBQAEggEAgld+rftjW8WmMwTJLX/3Kk9hQv9ZUufsieijxhnCo3gtR/6xaKdMC4wpYM9Eck7FFdmjz2XnJK9o5rlvjW5ZBH3u2A3tphs6cgy7HzsfrsJvw1Mc+CLSNL35MVi/YvNCxezn+rXn28NW8NntByoLTzZnd6iGxSBk4S7Z7XwvdQWuUjXy0muEeAUYtS/eppNZYdyeMpzE9oHmfMM+zwdOYzc/nfwvnoLHGP+sv6KmnzCyNtqyrdvCIn+m+ljPWpGvj410Q52Xili1Scgi+ALJf4xiEnD5c5YjEkYY8uUe4etCDYZ/aXp9RGvZiHD8Le6jz34fcWbLZlQacCfgcyY8AzBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBD4CRz8QLvbtgRx/NTxEnpfgCBLQD1ei8KAcd0LTT7sezZPt6LQnLxPuwx5StflI5xOgA==]" 158 | 159 | Scenario: no-decrypt mode should succeed even if keyfile is unreadable 160 | Given my EDITOR is set to "/bin/cat" 161 | When I run `bash -c 'cp test_edit.yaml test_edit.eyaml'` 162 | When I run `eyaml edit -d --pkcs7-private-key=not_a_keyfile test_edit.eyaml` 163 | Then the exit status should be 0 164 | And the stderr should not contain "No such file or directory" 165 | And the output should not match /DEC\(\d+\)/ 166 | And the output should match /encrypted_string: ENC\[PKCS7,/ 167 | 168 | Scenario: EDITOR has a space in it that isn't quoted or escaped 169 | Given my EDITOR is set to "./path/spaced editor.sh" 170 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 171 | When I run `eyaml edit test_input.eyaml` 172 | Then the stderr should contain "No changes detected" 173 | 174 | Scenario: EDITOR has a space in it that is escaped but not isn't quoted 175 | Given my EDITOR is set to "./path/spaced\ editor.sh" 176 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 177 | When I run `eyaml edit test_input.eyaml` 178 | Then the stderr should contain "No changes detected" 179 | 180 | Scenario: EDITOR has a space in it that is quoted 181 | Given my EDITOR is set to ""./path/spaced editor.sh"" 182 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 183 | When I run `eyaml edit test_input.eyaml` 184 | Then the stderr should contain "No changes detected" 185 | 186 | Scenario: EDITOR is an executable on PATH 187 | Given my EDITOR is set to "editor.sh" 188 | Given my PATH contains "./path" 189 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 190 | When I run `eyaml edit test_input.eyaml` 191 | Then the stderr should contain "No changes detected" 192 | 193 | Scenario: EDITOR is an executable on PATH and contains arguments 194 | Given my EDITOR is set to "editor.sh -c" 195 | Given my PATH contains "./path" 196 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 197 | When I run `eyaml edit test_input.eyaml` 198 | Then the output should match /editor\.sh" -c/ 199 | Then the stderr should contain "No changes detected" 200 | 201 | Scenario: EDITOR is an executable on PATH and has a space in it that isn't quoted or escaped 202 | Given my EDITOR is set to "spaced editor.sh" 203 | Given my PATH contains "./path" 204 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 205 | When I run `eyaml edit test_input.eyaml` 206 | Then the stderr should contain "No changes detected" 207 | 208 | Scenario: EDITOR is an executable on PATH and has a space in it that is escaped but not quoted 209 | Given my EDITOR is set to "spaced\ editor.sh" 210 | Given my PATH contains "./path" 211 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 212 | When I run `eyaml edit test_input.eyaml` 213 | Then the stderr should contain "No changes detected" 214 | 215 | Scenario: EDITOR is an executable on PATH and has a space in it that is quoted 216 | Given my EDITOR is set to ""spaced editor.sh"" 217 | Given my PATH contains "./path" 218 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 219 | When I run `eyaml edit test_input.eyaml` 220 | Then the stderr should contain "No changes detected" 221 | 222 | Scenario: EDITOR is an executable on PATH and has a space in it and contains arguments 223 | Given my EDITOR is set to "spaced editor.sh -c" 224 | Given my PATH contains "./path" 225 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 226 | When I run `eyaml edit test_input.eyaml` 227 | Then the output should match /spaced editor\.sh" -c/ 228 | Then the stderr should contain "No changes detected" 229 | 230 | Scenario: EDITOR is invalid 231 | Given my EDITOR is set to "does_not_exist.sh" 232 | When I run `bash -c 'cp test_input.yaml test_input.eyaml'` 233 | When I run `eyaml edit test_input.eyaml` 234 | Then the stderr should contain "Editor did not exit successfully" 235 | Then the stderr should not contain "Wrote temporary file" 236 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config --no-auto-gen-timestamp` 3 | # using RuboCop version 1.75.8. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # This cop supports safe autocorrection (--autocorrect). 11 | # Configuration parameters: AllowForAlignment. 12 | Layout/CommentIndentation: 13 | Exclude: 14 | - 'lib/hiera/backend/eyaml/subcommands/help.rb' 15 | 16 | # Offense count: 1 17 | Lint/DuplicateMethods: 18 | Exclude: 19 | - 'lib/hiera/backend/eyaml/subcommand.rb' 20 | 21 | # Offense count: 5 22 | # Configuration parameters: AllowedParentClasses. 23 | Lint/MissingSuper: 24 | Exclude: 25 | - 'lib/hiera/backend/eyaml/parser/encrypted_tokens.rb' 26 | 27 | # Offense count: 2 28 | # This cop supports unsafe autocorrection (--autocorrect-all). 29 | # Configuration parameters: AllowedImplicitNamespaces. 30 | # AllowedImplicitNamespaces: Gem 31 | Lint/RaiseException: 32 | Exclude: 33 | - 'lib/hiera/backend/eyaml_backend.rb' 34 | 35 | # Offense count: 1 36 | Lint/RescueException: 37 | Exclude: 38 | - 'lib/hiera/backend/eyaml/subcommand.rb' 39 | 40 | # Offense count: 1 41 | # This cop supports unsafe autocorrection (--autocorrect-all). 42 | # Configuration parameters: AutoCorrect. 43 | Lint/UselessMethodDefinition: 44 | Exclude: 45 | - 'lib/hiera/backend/eyaml/parser/token.rb' 46 | 47 | # Offense count: 1 48 | # This cop supports safe autocorrection (--autocorrect). 49 | # Configuration parameters: AutoCorrect, CheckForMethodsWithNoSideEffects. 50 | Lint/Void: 51 | Exclude: 52 | - 'features/support/env.rb' 53 | 54 | # Offense count: 1 55 | Naming/AccessorMethodName: 56 | Exclude: 57 | - 'lib/hiera/backend/eyaml/parser/encrypted_tokens.rb' 58 | 59 | # Offense count: 1 60 | # Configuration parameters: AllowedNames. 61 | # AllowedNames: module_parent 62 | Naming/ClassAndModuleCamelCase: 63 | Exclude: 64 | - 'lib/hiera/backend/eyaml_backend.rb' 65 | 66 | # Offense count: 1 67 | # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms. 68 | # CheckDefinitionPathHierarchyRoots: lib, spec, test, src 69 | # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS 70 | Naming/FileName: 71 | Exclude: 72 | - 'Rakefile.rb' 73 | - 'lib/hiera/backend/eyaml/CLI.rb' 74 | 75 | # Offense count: 3 76 | # Configuration parameters: ForbiddenDelimiters. 77 | # ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) 78 | Naming/HeredocDelimiterNaming: 79 | Exclude: 80 | - 'lib/hiera/backend/eyaml/subcommands/edit.rb' 81 | - 'lib/hiera/backend/eyaml/subcommands/help.rb' 82 | - 'lib/hiera/backend/eyaml/subcommands/unknown_command.rb' 83 | 84 | # Offense count: 1 85 | # Configuration parameters: EnforcedStyle, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. 86 | # SupportedStyles: snake_case, camelCase 87 | # ForbiddenIdentifiers: __id__, __send__ 88 | Naming/MethodName: 89 | Exclude: 90 | - 'lib/hiera/backend/eyaml/encryptors/pkcs7.rb' 91 | 92 | # Offense count: 1 93 | # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. 94 | # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to 95 | Naming/MethodParameterName: 96 | Exclude: 97 | - 'lib/hiera/backend/eyaml/parser/parser.rb' 98 | 99 | # Offense count: 1 100 | # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. 101 | # SupportedStyles: snake_case, normalcase, non_integer 102 | # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 103 | Naming/VariableNumber: 104 | Exclude: 105 | - 'lib/hiera/backend/eyaml/utils.rb' 106 | 107 | # Offense count: 1 108 | # This cop supports unsafe autocorrection (--autocorrect-all). 109 | Performance/Detect: 110 | Exclude: 111 | - 'lib/hiera/backend/eyaml/edithelper.rb' 112 | 113 | # Offense count: 5 114 | # This cop supports unsafe autocorrection (--autocorrect-all). 115 | Performance/MapCompact: 116 | Exclude: 117 | - 'lib/hiera/backend/eyaml/edithelper.rb' 118 | - 'lib/hiera/backend/eyaml/subcommands/decrypt.rb' 119 | - 'lib/hiera/backend/eyaml/subcommands/encrypt.rb' 120 | - 'lib/hiera/backend/eyaml/subcommands/help.rb' 121 | - 'lib/hiera/backend/eyaml/subcommands/unknown_command.rb' 122 | 123 | # Offense count: 1 124 | # This cop supports unsafe autocorrection (--autocorrect-all). 125 | # Configuration parameters: MaxKeyValuePairs. 126 | Performance/RedundantMerge: 127 | Exclude: 128 | - 'lib/hiera/backend/eyaml/logginghelper.rb' 129 | 130 | # Offense count: 1 131 | # This cop supports unsafe autocorrection (--autocorrect-all). 132 | Performance/StringInclude: 133 | Exclude: 134 | - 'lib/hiera/backend/eyaml/utils.rb' 135 | 136 | # Offense count: 1 137 | Security/Open: 138 | Exclude: 139 | - 'lib/hiera/backend/eyaml/encrypthelper.rb' 140 | 141 | # Offense count: 2 142 | # This cop supports unsafe autocorrection (--autocorrect-all). 143 | Security/YAMLLoad: 144 | Exclude: 145 | - 'features/step_definitions/decrypt_steps.rb' 146 | - 'lib/hiera/backend/eyaml_backend.rb' 147 | 148 | # Offense count: 8 149 | # This cop supports unsafe autocorrection (--autocorrect-all). 150 | # Configuration parameters: EnforcedStyle. 151 | # SupportedStyles: always, conditionals 152 | Style/AndOr: 153 | Exclude: 154 | - 'lib/hiera/backend/eyaml/encryptors/pkcs7.rb' 155 | - 'lib/hiera/backend/eyaml/logginghelper.rb' 156 | - 'lib/hiera/backend/eyaml/subcommand.rb' 157 | - 'lib/hiera/backend/eyaml_backend.rb' 158 | 159 | # Offense count: 1 160 | # This cop supports unsafe autocorrection (--autocorrect-all). 161 | # Configuration parameters: MinBranchesCount. 162 | Style/CaseLikeIf: 163 | Exclude: 164 | - 'lib/hiera/backend/eyaml_backend.rb' 165 | 166 | # Offense count: 18 167 | Style/ClassVars: 168 | Exclude: 169 | - 'lib/hiera/backend/eyaml.rb' 170 | - 'lib/hiera/backend/eyaml/commands.rb' 171 | - 'lib/hiera/backend/eyaml/options.rb' 172 | - 'lib/hiera/backend/eyaml/parser/encrypted_tokens.rb' 173 | - 'lib/hiera/backend/eyaml/plugins.rb' 174 | - 'lib/hiera/backend/eyaml/subcommand.rb' 175 | - 'lib/hiera/backend/eyaml/subcommands/unknown_command.rb' 176 | 177 | # Offense count: 1 178 | # This cop supports unsafe autocorrection (--autocorrect-all). 179 | # Configuration parameters: AllowedReceivers. 180 | Style/CollectionCompact: 181 | Exclude: 182 | - 'lib/hiera/backend/eyaml/parser/parser.rb' 183 | 184 | # Offense count: 35 185 | # Configuration parameters: AllowedConstants. 186 | Style/Documentation: 187 | Enabled: false 188 | 189 | # Offense count: 2 190 | # This cop supports unsafe autocorrection (--autocorrect-all). 191 | Style/EnvHome: 192 | Exclude: 193 | - 'lib/hiera/backend/eyaml/edithelper.rb' 194 | - 'lib/hiera/backend/eyaml/subcommand.rb' 195 | 196 | # Offense count: 10 197 | # This cop supports safe autocorrection (--autocorrect). 198 | # Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns. 199 | # SupportedStyles: annotated, template, unannotated 200 | Style/FormatStringToken: 201 | EnforcedStyle: unannotated 202 | 203 | # Offense count: 36 204 | # This cop supports unsafe autocorrection (--autocorrect-all). 205 | # Configuration parameters: EnforcedStyle. 206 | # SupportedStyles: always, always_true, never 207 | Style/FrozenStringLiteralComment: 208 | Enabled: false 209 | 210 | # Offense count: 3 211 | # This cop supports unsafe autocorrection (--autocorrect-all). 212 | Style/GlobalStdStream: 213 | Exclude: 214 | - 'lib/hiera/backend/eyaml/logginghelper.rb' 215 | - 'lib/hiera/backend/eyaml/subcommands/decrypt.rb' 216 | - 'lib/hiera/backend/eyaml/subcommands/encrypt.rb' 217 | 218 | # Offense count: 6 219 | # This cop supports unsafe autocorrection (--autocorrect-all). 220 | Style/IdenticalConditionalBranches: 221 | Exclude: 222 | - 'lib/hiera/backend/eyaml/subcommand.rb' 223 | - 'lib/hiera/backend/eyaml/subcommands/decrypt.rb' 224 | - 'lib/hiera/backend/eyaml/subcommands/edit.rb' 225 | 226 | # Offense count: 1 227 | # This cop supports unsafe autocorrection (--autocorrect-all). 228 | Style/MapIntoArray: 229 | Exclude: 230 | - 'lib/hiera/backend/eyaml_backend.rb' 231 | 232 | # Offense count: 1 233 | Style/MultilineBlockChain: 234 | Exclude: 235 | - 'lib/hiera/backend/eyaml/parser/parser.rb' 236 | 237 | # Offense count: 2 238 | # This cop supports unsafe autocorrection (--autocorrect-all). 239 | # Configuration parameters: EnforcedStyle. 240 | # SupportedStyles: literals, strict 241 | Style/MutableConstant: 242 | Exclude: 243 | - 'lib/hiera/backend/eyaml.rb' 244 | 245 | # Offense count: 4 246 | # This cop supports unsafe autocorrection (--autocorrect-all). 247 | # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. 248 | # SupportedStyles: predicate, comparison 249 | Style/NumericPredicate: 250 | Exclude: 251 | - 'spec/**/*' 252 | - 'lib/hiera/backend/eyaml/logginghelper.rb' 253 | - 'lib/hiera/backend/eyaml/parser/parser.rb' 254 | - 'lib/hiera/backend/eyaml/subcommands/edit.rb' 255 | - 'lib/hiera/backend/eyaml/utils.rb' 256 | 257 | # Offense count: 1 258 | # This cop supports unsafe autocorrection (--autocorrect-all). 259 | # Configuration parameters: EnforcedStyle. 260 | # SupportedStyles: short, verbose 261 | Style/PreferredHashMethods: 262 | Exclude: 263 | - 'lib/hiera/backend/eyaml/subcommand.rb' 264 | 265 | # Offense count: 1 266 | # This cop supports unsafe autocorrection (--autocorrect-all). 267 | # Configuration parameters: Methods. 268 | Style/RedundantArgument: 269 | Exclude: 270 | - 'lib/hiera/backend/eyaml/edithelper.rb' 271 | 272 | # Offense count: 1 273 | # This cop supports unsafe autocorrection (--autocorrect-all). 274 | Style/RedundantFilterChain: 275 | Exclude: 276 | - 'lib/hiera/backend/eyaml/edithelper.rb' 277 | 278 | # Offense count: 1 279 | # This cop supports unsafe autocorrection (--autocorrect-all). 280 | # Configuration parameters: AutoCorrect, AllowComments. 281 | Style/RedundantInitialize: 282 | Exclude: 283 | - 'lib/hiera/backend/eyaml/parser/token.rb' 284 | 285 | # Offense count: 6 286 | # This cop supports unsafe autocorrection (--autocorrect-all). 287 | Style/RedundantInterpolation: 288 | Exclude: 289 | - 'features/support/env.rb' 290 | - 'lib/hiera/backend/eyaml/encrypthelper.rb' 291 | - 'lib/hiera/backend/eyaml/subcommand.rb' 292 | - 'lib/hiera/backend/eyaml/subcommands/edit.rb' 293 | - 'lib/hiera/backend/eyaml/subcommands/recrypt.rb' 294 | 295 | # Offense count: 1 296 | # This cop supports safe autocorrection (--autocorrect). 297 | Style/RedundantSelf: 298 | Exclude: 299 | - 'lib/hiera/backend/eyaml/logginghelper.rb' 300 | 301 | # Offense count: 1 302 | # This cop supports unsafe autocorrection (--autocorrect-all). 303 | # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. 304 | # AllowedMethods: present?, blank?, presence, try, try! 305 | Style/SafeNavigation: 306 | Exclude: 307 | - 'lib/hiera/backend/eyaml/subcommand.rb' 308 | 309 | # Offense count: 1 310 | # This cop supports unsafe autocorrection (--autocorrect-all). 311 | Style/SelectByRegexp: 312 | Exclude: 313 | - 'hiera-eyaml.gemspec' 314 | 315 | # Offense count: 1 316 | # This cop supports unsafe autocorrection (--autocorrect-all). 317 | Style/SlicingWithRange: 318 | Exclude: 319 | - 'lib/hiera/backend/eyaml/edithelper.rb' 320 | 321 | # Offense count: 4 322 | # This cop supports unsafe autocorrection (--autocorrect-all). 323 | # Configuration parameters: RequireEnglish, EnforcedStyle. 324 | # SupportedStyles: use_perl_names, use_english_names, use_builtin_english_names 325 | Style/SpecialGlobalVars: 326 | Exclude: 327 | - 'hiera-eyaml.gemspec' 328 | - 'lib/hiera/backend/eyaml/subcommands/edit.rb' 329 | 330 | # Offense count: 1 331 | # This cop supports safe autocorrection (--autocorrect). 332 | Style/StderrPuts: 333 | Exclude: 334 | - 'lib/hiera/backend/eyaml/logginghelper.rb' 335 | 336 | # Offense count: 11 337 | # This cop supports unsafe autocorrection (--autocorrect-all). 338 | # Configuration parameters: Mode. 339 | Style/StringConcatenation: 340 | Exclude: 341 | - 'features/support/env.rb' 342 | - 'lib/hiera/backend/eyaml/edithelper.rb' 343 | - 'lib/hiera/backend/eyaml/parser/encrypted_tokens.rb' 344 | - 'lib/hiera/backend/eyaml/subcommand.rb' 345 | - 'lib/hiera/backend/eyaml/subcommands/decrypt.rb' 346 | - 'lib/hiera/backend/eyaml/subcommands/help.rb' 347 | - 'lib/hiera/backend/eyaml/subcommands/unknown_command.rb' 348 | - 'lib/hiera/backend/eyaml/utils.rb' 349 | 350 | # Offense count: 9 351 | # This cop supports unsafe autocorrection (--autocorrect-all). 352 | # Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. 353 | # AllowedMethods: define_method 354 | Style/SymbolProc: 355 | Exclude: 356 | - 'features/step_definitions/recrypt_steps.rb' 357 | - 'lib/hiera/backend/eyaml/parser/parser.rb' 358 | - 'lib/hiera/backend/eyaml/subcommands/decrypt.rb' 359 | - 'lib/hiera/backend/eyaml/subcommands/edit.rb' 360 | - 'lib/hiera/backend/eyaml/subcommands/encrypt.rb' 361 | - 'lib/hiera/backend/eyaml/utils.rb' 362 | - 'lib/hiera/backend/eyaml_backend.rb' 363 | 364 | # Offense count: 1 365 | # This cop supports unsafe autocorrection (--autocorrect-all). 366 | Style/ZeroLengthPredicate: 367 | Exclude: 368 | - 'lib/hiera/backend/eyaml/parser/parser.rb' 369 | 370 | # Offense count: 13 371 | # This cop supports safe autocorrection (--autocorrect). 372 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. 373 | # URISchemes: http, https 374 | Layout/LineLength: 375 | Max: 194 376 | --------------------------------------------------------------------------------