clarenceb-hiera-eyaml 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +10 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +52 -0
  5. data/LICENSE.txt +21 -0
  6. data/PLUGINS.md +4 -0
  7. data/README.md +322 -0
  8. data/Rakefile +1 -0
  9. data/bin/eyaml +13 -0
  10. data/hiera-eyaml.gemspec +22 -0
  11. data/lib/hiera/backend/eyaml/CLI.rb +60 -0
  12. data/lib/hiera/backend/eyaml/commands.rb +21 -0
  13. data/lib/hiera/backend/eyaml/encryptor.rb +79 -0
  14. data/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +107 -0
  15. data/lib/hiera/backend/eyaml/options.rb +35 -0
  16. data/lib/hiera/backend/eyaml/parser/encrypted_tokens.rb +138 -0
  17. data/lib/hiera/backend/eyaml/parser/parser.rb +82 -0
  18. data/lib/hiera/backend/eyaml/parser/token.rb +49 -0
  19. data/lib/hiera/backend/eyaml/plugins.rb +70 -0
  20. data/lib/hiera/backend/eyaml/subcommand.rb +126 -0
  21. data/lib/hiera/backend/eyaml/subcommands/createkeys.rb +29 -0
  22. data/lib/hiera/backend/eyaml/subcommands/decrypt.rb +81 -0
  23. data/lib/hiera/backend/eyaml/subcommands/edit.rb +105 -0
  24. data/lib/hiera/backend/eyaml/subcommands/encrypt.rb +100 -0
  25. data/lib/hiera/backend/eyaml/subcommands/help.rb +51 -0
  26. data/lib/hiera/backend/eyaml/subcommands/recrypt.rb +56 -0
  27. data/lib/hiera/backend/eyaml/subcommands/unknown_command.rb +48 -0
  28. data/lib/hiera/backend/eyaml/subcommands/version.rb +47 -0
  29. data/lib/hiera/backend/eyaml/utils.rb +172 -0
  30. data/lib/hiera/backend/eyaml.rb +48 -0
  31. data/lib/hiera/backend/eyaml_backend.rb +125 -0
  32. data/sublime_text/README.md +16 -0
  33. data/sublime_text/eyaml.sublime-package +0 -0
  34. data/sublime_text/eyaml.syntax_definition.json +288 -0
  35. data/tools/regem.sh +9 -0
  36. metadata +114 -0
@@ -0,0 +1,81 @@
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
+
12
+ class Decrypt < Subcommand
13
+
14
+ def self.options
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 => :eyaml,
24
+ :description => "Source input is an eyaml file",
25
+ :short => 'e',
26
+ :type => :string},
27
+ {:name => :stdin,
28
+ :description => "Source input is taken from stdin",
29
+ :short => :none}
30
+ ]
31
+ end
32
+
33
+ def self.description
34
+ "decrypt some data"
35
+ end
36
+
37
+ def self.validate options
38
+ sources = [:eyaml, :password, :string, :file, :stdin].collect {|x| x if options[x]}.compact
39
+ Trollop::die "You must specify a source" if sources.count.zero?
40
+ Trollop::die "You can only specify one of (#{sources.join(', ')})" if sources.count > 1
41
+ options[:source] = sources.first
42
+
43
+ options[:input_data] = case options[:source]
44
+ when :stdin
45
+ STDIN.read
46
+ when :string
47
+ options[:string]
48
+ when :file
49
+ File.read options[:file]
50
+ when :eyaml
51
+ File.read options[:eyaml]
52
+ end
53
+ options
54
+ end
55
+
56
+ def self.execute
57
+ parser = Parser::ParserFactory.encrypted_parser
58
+ tokens = parser.parse(Eyaml::Options[:input_data])
59
+ case Eyaml::Options[:source]
60
+ when :eyaml
61
+ decrypted = tokens.map{ |token| token.to_decrypted }
62
+ decrypted.join
63
+ else
64
+ decrypted = tokens.map{ |token|
65
+ case token.class.name
66
+ when /::EncToken$/
67
+ token.plain_text
68
+ else
69
+ token.match
70
+ end
71
+ }
72
+ decrypted.join
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,105 @@
1
+ require 'hiera/backend/eyaml/utils'
2
+ require 'hiera/backend/eyaml/options'
3
+ require 'hiera/backend/eyaml/parser/parser'
4
+ require 'hiera/backend/eyaml/subcommand'
5
+ require 'highline/import'
6
+
7
+ class Hiera
8
+ module Backend
9
+ module Eyaml
10
+ module Subcommands
11
+
12
+ class Edit < Subcommand
13
+
14
+ def self.options
15
+ []
16
+ end
17
+
18
+ def self.description
19
+ "edit an eyaml file"
20
+ end
21
+
22
+ def self.helptext
23
+ "Usage: eyaml edit [options] <some-eyaml-file>"
24
+ end
25
+
26
+ def self.validate options
27
+ Trollop::die "You must specify an eyaml file" if ARGV.empty?
28
+ options[:source] = :eyaml
29
+ options[:eyaml] = ARGV.shift
30
+ options[:input_data] = File.read options[:eyaml]
31
+ options
32
+ end
33
+
34
+ def self.execute
35
+ encrypted_parser = Parser::ParserFactory.encrypted_parser
36
+ tokens = encrypted_parser.parse Eyaml::Options[:input_data]
37
+ decrypted_input = tokens.each_with_index.to_a.map{|(t,index)| t.to_decrypted :index => index}.join
38
+ decrypted_file = Utils.write_tempfile decrypted_input
39
+
40
+ editor = Utils.find_editor
41
+
42
+ begin
43
+ system "#{editor} #{decrypted_file}"
44
+ status = $?
45
+
46
+ raise StandardError, "File was moved by editor" unless File.file? decrypted_file
47
+ edited_file = File.read decrypted_file
48
+
49
+ raise StandardError, "Editor #{editor} has not exited?" unless status.exited?
50
+ raise StandardError, "Editor did not exit successfully (exit code #{status.exitstatus}), aborting" unless status.exitstatus == 0
51
+ raise StandardError, "Edited file is blank" if edited_file.empty?
52
+
53
+ if edited_file == decrypted_input
54
+ Utils.info "No changes detected, exiting"
55
+ else
56
+ decrypted_parser = Parser::ParserFactory.decrypted_parser
57
+ edited_tokens = decrypted_parser.parse(edited_file)
58
+
59
+ # check that the tokens haven't been copy / pasted
60
+ used_ids = edited_tokens.find_all{ |t| t.class.name =~ /::EncToken$/ and !t.id.nil? }.map{ |t| t.id }
61
+ if used_ids.length != used_ids.uniq.length
62
+ raise RecoverableError, "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 in parentheses"
63
+ end
64
+
65
+ # replace untouched values with the source values
66
+ edited_denoised_tokens = edited_tokens.map{ |token|
67
+ if token.class.name =~ /::EncToken$/ && !token.id.nil?
68
+ old_token = tokens[token.id]
69
+ if old_token.plain_text.eql? token.plain_text
70
+ old_token
71
+ else
72
+ token
73
+ end
74
+ else
75
+ token
76
+ end
77
+ }
78
+
79
+ encrypted_output = edited_denoised_tokens.map{ |t| t.to_encrypted }.join
80
+
81
+ filename = Eyaml::Options[:eyaml]
82
+ File.open("#{filename}", 'w') { |file|
83
+ file.write encrypted_output
84
+ }
85
+ end
86
+ rescue RecoverableError => e
87
+ Utils.info e
88
+ if agree "Return to the editor to try again?"
89
+ retry
90
+ else
91
+ raise e
92
+ end
93
+ ensure
94
+ Utils.secure_file_delete :file => decrypted_file, :num_bytes => [edited_file.length, decrypted_input.length].max
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,100 @@
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
+
11
+ class Encrypt < Subcommand
12
+
13
+ def self.options
14
+ [{:name => :password,
15
+ :description => "Source input is a password entered on the terminal",
16
+ :short => 'p'},
17
+ {:name => :string,
18
+ :description => "Source input is a string provided as an argument",
19
+ :short => 's',
20
+ :type => :string},
21
+ {:name => :file,
22
+ :description => "Source input is a regular file",
23
+ :short => 'f',
24
+ :type => :string},
25
+ {:name => :stdin,
26
+ :description => "Source input is taken from stdin",
27
+ :short => :none},
28
+ {:name => :eyaml,
29
+ :description => "Source input is an eyaml file",
30
+ :short => 'e',
31
+ :type => :string},
32
+ {:name => :output,
33
+ :description => "Output format of final result (examples, block, string)",
34
+ :type => :string,
35
+ :short => 'o',
36
+ :default => "examples"},
37
+ {:name => :label,
38
+ :description => "Apply a label to the encrypted result",
39
+ :short => 'l',
40
+ :type => :string}
41
+ ]
42
+ end
43
+
44
+ def self.description
45
+ "encrypt some data"
46
+ end
47
+
48
+ def self.validate options
49
+ sources = [:password, :string, :file, :stdin, :eyaml].collect {|x| x if options[x]}.compact
50
+ Trollop::die "You must specify a source" if sources.count.zero?
51
+ Trollop::die "You can only specify one of (#{sources.join(', ')})" if sources.count > 1
52
+ options[:source] = sources.first
53
+
54
+ options[:input_data] = case options[:source]
55
+ when :password
56
+ Utils.read_password
57
+ when :string
58
+ options[:string]
59
+ when :file
60
+ File.read options[:file]
61
+ when :stdin
62
+ STDIN.read
63
+ when :eyaml
64
+ File.read options[:eyaml]
65
+ end
66
+ options
67
+
68
+ end
69
+
70
+ def self.execute
71
+ case Eyaml::Options[:source]
72
+ when :eyaml
73
+ parser = Parser::ParserFactory.decrypted_parser
74
+ tokens = parser.parse(Eyaml::Options[:input_data])
75
+ encrypted = tokens.map{ |token| token.to_encrypted }
76
+ encrypted.join
77
+ else
78
+ encryptor = Encryptor.find
79
+ ciphertext = encryptor.encode( encryptor.encrypt(Eyaml::Options[:input_data]) )
80
+ token = Parser::EncToken.new(:block, Eyaml::Options[:input_data], encryptor, ciphertext, nil, ' ')
81
+ case Eyaml::Options[:output]
82
+ when "block"
83
+ token.to_encrypted :label => Eyaml::Options[:label], :use_chevron => !Eyaml::Options[:label].nil?, :format => :block
84
+ when "string"
85
+ token.to_encrypted :label => Eyaml::Options[:label], :format => :string
86
+ when "examples"
87
+ string = token.to_encrypted :label => Eyaml::Options[:label] || 'string', :format => :string
88
+ block = token.to_encrypted :label => Eyaml::Options[:label] || 'block', :format => :block
89
+ "#{string}\n\nOR\n\n#{block}"
90
+ else
91
+ token.to_encrypted :format => :string
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,51 @@
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
+
9
+ class Help < Subcommand
10
+
11
+ def self.options
12
+ []
13
+ end
14
+
15
+ def self.description
16
+ "this page"
17
+ end
18
+
19
+ def self.execute
20
+
21
+ puts <<-EOS
22
+ Welcome to eyaml #{Eyaml::VERSION}
23
+
24
+ Usage:
25
+ eyaml subcommand [global-opts] [subcommand-opts]
26
+
27
+ Available subcommands:
28
+ #{Eyaml.subcommands.collect {|command|
29
+ command_class = Subcommands.const_get(Utils.camelcase command)
30
+ sprintf "%15s: %-65s", command.downcase, command_class.description unless command_class.hidden?
31
+ }.compact.join("\n")}
32
+
33
+ For more help on an individual command, use --help on that command
34
+
35
+ Installed Plugins:
36
+ #{Plugins.plugins.collect {|plugin|
37
+ "\t" + plugin.name.split("hiera-eyaml-").last
38
+ }.join("\n")}
39
+ EOS
40
+ end
41
+
42
+ def self.hidden?
43
+ true
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
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
+
10
+ class Recrypt < Subcommand
11
+
12
+ def self.options
13
+ []
14
+ end
15
+
16
+ def self.description
17
+ "recrypt an eyaml file"
18
+ end
19
+
20
+ def self.helptext
21
+ "Usage: eyaml recrypt [options] <some-eyaml-file>"
22
+ end
23
+
24
+ def self.validate options
25
+ Trollop::die "You must specify an eyaml file" if ARGV.empty?
26
+ options[:source] = :eyaml
27
+ options[:eyaml] = ARGV.shift
28
+ options[:input_data] = File.read options[:eyaml]
29
+ options
30
+ end
31
+
32
+ def self.execute
33
+
34
+ encrypted_parser = Parser::ParserFactory.encrypted_parser
35
+ tokens = encrypted_parser.parse Eyaml::Options[:input_data]
36
+ decrypted_input = tokens.each_with_index.to_a.map{|(t,index)| t.to_decrypted :index => index}.join
37
+
38
+ decrypted_parser = Parser::ParserFactory.decrypted_parser
39
+ edited_tokens = decrypted_parser.parse(decrypted_input)
40
+
41
+ encrypted_output = edited_tokens.map{ |t| t.to_encrypted }.join
42
+
43
+ filename = Eyaml::Options[:eyaml]
44
+ File.open("#{filename}", 'w') { |file|
45
+ file.write encrypted_output
46
+ }
47
+
48
+ nil
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,48 @@
1
+ require 'hiera/backend/eyaml/subcommand'
2
+
3
+ class Hiera
4
+ module Backend
5
+ module Eyaml
6
+ module Subcommands
7
+
8
+ class UnknownCommand < Eyaml::Subcommand
9
+
10
+ class << self
11
+ attr_accessor :original_command
12
+ end
13
+
14
+ @@original_command = "unknown"
15
+
16
+ def self.options
17
+ []
18
+ end
19
+
20
+ def self.description
21
+ "Unknown command (#{@@original_command})"
22
+ end
23
+
24
+ def self.execute
25
+ subcommands = Eyaml.subcommands
26
+ puts <<-EOS
27
+ Unknown subcommand#{ ": " + Eyaml.subcommand if Eyaml.subcommand }
28
+
29
+ Usage: eyaml <subcommand>
30
+
31
+ Please use one of the following subcommands or help for more help:
32
+ #{Eyaml.subcommands.sort.collect {|command|
33
+ command_class = Subcommands.const_get(Utils.camelcase command)
34
+ command unless command_class.hidden?
35
+ }.compact.join(", ")}
36
+ EOS
37
+ end
38
+
39
+ def self.hidden?
40
+ true
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
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
+
9
+ class Version < Subcommand
10
+
11
+ def self.options
12
+ []
13
+ end
14
+
15
+ def self.description
16
+ "show version information"
17
+ end
18
+
19
+ def self.execute
20
+ plugin_versions = {}
21
+
22
+ puts <<-EOS
23
+ Version info
24
+
25
+ hiera-eyaml (core): #{Eyaml::VERSION}
26
+ EOS
27
+
28
+ Plugins.plugins.each do |plugin|
29
+ plugin_shortname = plugin.name.split("hiera-eyaml-").last
30
+ plugin_version = begin
31
+ Encryptor.find(plugin_shortname)::VERSION.to_s
32
+ rescue
33
+ "unknown (is plugin compatible with eyaml 2.0+ ?)"
34
+ end
35
+ puts " hiera-eyaml-#{plugin_shortname} (gem): #{plugin_version}"
36
+ end
37
+
38
+ nil
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,172 @@
1
+ require 'highline/import'
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+
5
+ class Hiera
6
+ module Backend
7
+ module Eyaml
8
+ class Utils
9
+
10
+ def self.read_password
11
+ ask("Enter password: ") {|q| q.echo = "*" }
12
+ end
13
+
14
+ def self.confirm? message
15
+ result = ask("#{message} (y/N): ")
16
+ if result.downcase == "y" or result.downcase == "yes"
17
+ true
18
+ else
19
+ false
20
+ end
21
+ end
22
+
23
+ def self.camelcase string
24
+ return string if string !~ /_/ && string =~ /[A-Z]+.*/
25
+ string.split('_').map{|e| e.capitalize}.join
26
+ end
27
+
28
+ def self.snakecase string
29
+ return string if string !~ /[A-Z]/
30
+ string.split(/(?=[A-Z])/).collect {|x| x.downcase}.join("_")
31
+ end
32
+
33
+ def self.find_editor
34
+ editor = ENV['EDITOR']
35
+ editor ||= %w{ /usr/bin/sensible-editor /usr/bin/editor /usr/bin/vim /usr/bin/vi }.collect {|e| e if FileTest.executable? e}.compact.first
36
+ raise StandardError, "Editor not found. Please set your EDITOR env variable" if editor.nil?
37
+ editor
38
+ end
39
+
40
+ def self.secure_file_delete args
41
+ file = File.open(args[:file], 'r+')
42
+ num_bytes = args[:num_bytes]
43
+ [0xff, 0x55, 0xaa, 0x00].each do |byte|
44
+ file.seek(0, IO::SEEK_SET)
45
+ num_bytes.times { file.print(byte.chr) }
46
+ file.fsync
47
+ end
48
+ File.delete args[:file]
49
+ end
50
+
51
+ def self.write_tempfile data_to_write
52
+ file = Tempfile.open('eyaml_edit')
53
+ path = file.path
54
+ file.close!
55
+
56
+ file = File.open(path, "w")
57
+ file.puts data_to_write
58
+ file.close
59
+
60
+ path
61
+ end
62
+
63
+ def self.write_important_file args
64
+ filename = args[ :filename ]
65
+ content = args[ :content ]
66
+ mode = args[ :mode ]
67
+ if File.file? "#{filename}"
68
+ raise StandardError, "User aborted" unless Utils::confirm? "Are you sure you want to overwrite \"#{filename}\"?"
69
+ end
70
+ open( "#{filename}", "w" ) do |io|
71
+ io.write(content)
72
+ end
73
+ File.chmod( mode, filename ) unless mode.nil?
74
+ end
75
+
76
+ def self.ensure_key_dir_exists key_file
77
+ key_dir = File.dirname key_file
78
+
79
+ unless File.directory? key_dir
80
+ begin
81
+ FileUtils.mkdir_p key_dir
82
+ Utils::info "Created key directory: #{key_dir}"
83
+ rescue
84
+ raise StandardError, "Cannot create key directory: #{key_dir}"
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ def self.find_closest_class args
91
+ parent_class = args[ :parent_class ]
92
+ class_name = args[ :class_name ]
93
+ constants = parent_class.constants
94
+ candidates = []
95
+ constants.each do | candidate |
96
+ candidates << candidate.to_s if candidate.to_s.downcase == class_name.downcase
97
+ end
98
+ if candidates.count > 0
99
+ parent_class.const_get candidates.first
100
+ else
101
+ nil
102
+ end
103
+ end
104
+
105
+ def self.require_dir classdir
106
+ num_class_hierarchy_levels = self.to_s.split("::").count - 1
107
+ root_folder = File.dirname(__FILE__) + "/" + Array.new(num_class_hierarchy_levels).fill("..").join("/")
108
+ class_folder = root_folder + "/" + classdir
109
+ Dir[File.expand_path("#{class_folder}/*.rb")].uniq.each do |file|
110
+ # puts "Requiring file: #{file}"
111
+ require file
112
+ end
113
+ end
114
+
115
+ def self.find_all_subclasses_of args
116
+ parent_class = args[ :parent_class ]
117
+ constants = parent_class.constants
118
+ candidates = []
119
+ constants.each do | candidate |
120
+ candidates << candidate.to_s.split('::').last if parent_class.const_get(candidate).class.to_s == "Class"
121
+ end
122
+ candidates
123
+ end
124
+
125
+ def self.hiera?
126
+ "hiera".eql? Eyaml::Options[:source]
127
+ end
128
+
129
+ def self.structure_message messageinfo
130
+ message = {:from => "hiera-eyaml-core"}
131
+ case messageinfo.class.to_s
132
+ when 'Hash'
133
+ message.merge!(messageinfo)
134
+ else
135
+ message.merge!({:msg => messageinfo.to_s})
136
+ end
137
+ end
138
+
139
+ def self.warn messageinfo
140
+ message = self.structure_message messageinfo
141
+ message = "[#{message[:from]}] !!! #{message[:msg]}"
142
+ if self.hiera?
143
+ Hiera.warn format_message msg
144
+ else
145
+ STDERR.puts message
146
+ end
147
+ end
148
+
149
+ def self.info messageinfo
150
+ message = self.structure_message messageinfo
151
+ message = "[#{message[:from]}] #{message[:msg]}"
152
+ if self.hiera?
153
+ Hiera.debug message if Eyaml.verbosity_level > 0
154
+ else
155
+ STDERR.puts message if Eyaml.verbosity_level > 0
156
+ end
157
+ end
158
+
159
+ def self.debug messageinfo
160
+ message = self.structure_message messageinfo
161
+ message = "[#{message[:from]}] #{message[:msg]}"
162
+ if self.hiera?
163
+ Hiera.debug message if Eyaml.verbosity_level > 1
164
+ else
165
+ STDERR.puts message if Eyaml.verbosity_level > 1
166
+ end
167
+ end
168
+
169
+ end
170
+ end
171
+ end
172
+ end