clarenceb-hiera-eyaml 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,107 @@
1
+ require 'openssl'
2
+ require 'hiera/backend/eyaml/encryptor'
3
+ require 'hiera/backend/eyaml/utils'
4
+ require 'hiera/backend/eyaml/options'
5
+
6
+ class Hiera
7
+ module Backend
8
+ module Eyaml
9
+ module Encryptors
10
+
11
+ class Pkcs7 < Encryptor
12
+
13
+ self.options = {
14
+ :private_key => { :desc => "Path to private key",
15
+ :type => :string,
16
+ :default => "./keys/private_key.pkcs7.pem" },
17
+ :public_key => { :desc => "Path to public key",
18
+ :type => :string,
19
+ :default => "./keys/public_key.pkcs7.pem" }
20
+ }
21
+
22
+ self.tag = "PKCS7"
23
+
24
+ def self.encrypt plaintext
25
+
26
+ public_key = self.option :public_key
27
+ raise StandardError, "pkcs7_public_key is not defined" unless public_key
28
+
29
+ public_key_pem = File.read public_key
30
+ public_key_x509 = OpenSSL::X509::Certificate.new( public_key_pem )
31
+
32
+ cipher = OpenSSL::Cipher::AES.new(256, :CBC)
33
+ OpenSSL::PKCS7::encrypt([public_key_x509], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der
34
+
35
+ end
36
+
37
+ def self.decrypt ciphertext
38
+
39
+ public_key = self.option :public_key
40
+ private_key = self.option :private_key
41
+ raise StandardError, "pkcs7_public_key is not defined" unless public_key
42
+ raise StandardError, "pkcs7_private_key is not defined" unless private_key
43
+
44
+ private_key_pem = File.read private_key
45
+ private_key_rsa = OpenSSL::PKey::RSA.new( private_key_pem )
46
+
47
+ public_key_pem = File.read public_key
48
+ public_key_x509 = OpenSSL::X509::Certificate.new( public_key_pem )
49
+
50
+ pkcs7 = OpenSSL::PKCS7.new( ciphertext )
51
+ pkcs7.decrypt(private_key_rsa, public_key_x509)
52
+
53
+ end
54
+
55
+ def self.create_keys
56
+
57
+ # Try to do equivalent of:
58
+ # openssl req -x509 -nodes -days 100000 -newkey rsa:2048 -keyout privatekey.pem -out publickey.pem -subj '/'
59
+
60
+ public_key = self.option :public_key
61
+ private_key = self.option :private_key
62
+
63
+ key = OpenSSL::PKey::RSA.new(2048)
64
+ Utils.ensure_key_dir_exists private_key
65
+ Utils.write_important_file :filename => private_key, :content => key.to_pem, :mode => 0600
66
+
67
+ name = OpenSSL::X509::Name.parse("/DC=org/DC=example/CN=eyaml")
68
+ cert = OpenSSL::X509::Certificate.new()
69
+ cert.serial = 0
70
+ cert.version = 2
71
+ cert.subject = name
72
+ cert.issuer = cert.subject
73
+ cert.not_before = Time.now
74
+ cert.not_after = if 1.size == 8 # 64bit
75
+ Time.now + 50 * 365 * 24 * 60 * 60
76
+ else # 32bit
77
+ Time.at(0x7fffffff)
78
+ end
79
+ cert.public_key = key.public_key
80
+
81
+ ef = OpenSSL::X509::ExtensionFactory.new
82
+ ef.subject_certificate = cert
83
+ ef.issuer_certificate = cert
84
+ cert.extensions = [
85
+ ef.create_extension("basicConstraints","CA:TRUE", true),
86
+ ef.create_extension("subjectKeyIdentifier", "hash"),
87
+ ]
88
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
89
+ "keyid:always,issuer:always")
90
+
91
+ cert.sign key, OpenSSL::Digest::SHA1.new
92
+
93
+ Utils.ensure_key_dir_exists public_key
94
+ Utils.write_important_file :filename => public_key, :content => cert.to_pem
95
+ puts "Keys created OK"
96
+
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+
107
+ end
@@ -0,0 +1,35 @@
1
+ class Hiera
2
+ module Backend
3
+ module Eyaml
4
+ class Options
5
+
6
+ def self.[]= key, value
7
+ @@options ||= {}
8
+ @@options[ key.to_sym ] = value
9
+ end
10
+
11
+ def self.[] key
12
+ @@options ||= {}
13
+ @@options[ key.to_sym ]
14
+ end
15
+
16
+ def self.set hash
17
+ @@options = {}
18
+ hash.each do |k, v|
19
+ @@options[ k.to_sym ] = v
20
+ end
21
+ end
22
+
23
+ def self.debug
24
+ Utils::debug "Dump of eyaml tool options dict:"
25
+ Utils::debug "--------------------------------"
26
+ @@options.each do |k, v|
27
+ Utils::debug sprintf "%18s %-18s = %-18s %-18s", "(#{k.class.name})", k.to_s, v.to_s, "(#{v.class.name})"
28
+ end
29
+ Utils::debug "--------------------------------"
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,138 @@
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
+
6
+
7
+ class Hiera
8
+ module Backend
9
+ module Eyaml
10
+ module Parser
11
+ class EncToken < Token
12
+ attr_reader :format, :cipher, :encryptor, :indentation, :plain_text, :id
13
+ def self.encrypted_value(format, encryption_scheme, cipher, match, indentation = '')
14
+ decryptor = Encryptor.find encryption_scheme
15
+ plain_text = decryptor.decrypt( decryptor.decode cipher )
16
+ EncToken.new(format, plain_text, decryptor, cipher, match, indentation)
17
+ end
18
+ def self.decrypted_value(format, plain_text, encryption_scheme, match, id, indentation = '')
19
+ encryptor = Encryptor.find encryption_scheme
20
+ cipher = encryptor.encode( encryptor.encrypt plain_text )
21
+ id_number = id.nil? ? nil : id.gsub(/\(|\)/, "").to_i
22
+ EncToken.new(format, plain_text, encryptor, cipher, match, indentation, id_number)
23
+ end
24
+
25
+ def initialize(format, plain_text, encryptor, cipher, match = '', indentation = '', id = nil)
26
+ @format = format
27
+ @plain_text = plain_text
28
+ @encryptor = encryptor
29
+ @cipher = cipher
30
+ @indentation = indentation
31
+ @id = id
32
+ super(match)
33
+ end
34
+
35
+ def to_encrypted(args={})
36
+ label = args[:label]
37
+ label_string = label.nil? ? '' : "#{label}: "
38
+ format = args[:format].nil? ? @format : args[:format]
39
+ case format
40
+ when :block
41
+ # strip any white space
42
+ @cipher = @cipher.gsub(/ /m, "")
43
+ # normalize indentation
44
+ ciphertext = @cipher.gsub(/\n/, "\n" + @indentation)
45
+ chevron = (args[:use_chevron].nil? || args[:use_chevron]) ? ">\n" : ''
46
+ "#{label_string}#{chevron}" + @indentation + "ENC[#{@encryptor.tag},#{ciphertext}]"
47
+ when :string
48
+ ciphertext = @cipher.gsub(/\n/, "")
49
+ "#{label_string}ENC[#{@encryptor.tag},#{ciphertext}]"
50
+ else
51
+ raise "#{@format} is not a valid format"
52
+ end
53
+ end
54
+
55
+ def to_decrypted(args={})
56
+ label = args[:label]
57
+ label_string = label.nil? ? '' : "#{label}: "
58
+ format = args[:format].nil? ? @format : args[:format]
59
+ index = args[:index].nil? ? '' : "(#{args[:index]})"
60
+ case format
61
+ when :block
62
+ chevron = (args[:use_chevron].nil? || args[:use_chevron]) ? ">\n" : ''
63
+ "#{label_string}#{chevron}" + indentation + "DEC#{index}::#{@encryptor.tag}[" + @plain_text + "]!"
64
+ when :string
65
+ "#{label_string}DEC#{index}::#{@encryptor.tag}[" + @plain_text + "]!"
66
+ else
67
+ raise "#{@format} is not a valid format"
68
+ end
69
+ end
70
+
71
+ def to_plain_text
72
+ @plain_text
73
+ end
74
+
75
+ end
76
+
77
+ class EncTokenType < TokenType
78
+ def create_enc_token(match, type, enc_comma, cipher, indentation = '')
79
+ encryption_scheme = enc_comma.nil? ? Eyaml.default_encryption_scheme : enc_comma.split(",").first
80
+ EncToken.encrypted_value(type, encryption_scheme, cipher, match, indentation)
81
+ end
82
+ end
83
+
84
+ class EncHieraTokenType < EncTokenType
85
+ def initialize
86
+ @regex = /ENC\[(\w+,)?([a-zA-Z0-9\+\/ =\n]+?)\]/
87
+ @string_token_type = EncStringTokenType.new()
88
+ end
89
+ def create_token(string)
90
+ @string_token_type.create_token(string.gsub(/[ \n]/, ''))
91
+ end
92
+ end
93
+
94
+ class EncStringTokenType < EncTokenType
95
+ def initialize
96
+ @regex = /ENC\[(\w+,)?([a-zA-Z0-9\+\/=]+?)\]/
97
+ end
98
+ def create_token(string)
99
+ md = @regex.match(string)
100
+ self.create_enc_token(string, :string, md[1], md[2])
101
+ end
102
+ end
103
+
104
+ class EncBlockTokenType < EncTokenType
105
+ def initialize
106
+ @regex = />\n(\s*)ENC\[(\w+,)?([a-zA-Z0-9\+\/ =\n]+?)\]/
107
+ end
108
+ def create_token(string)
109
+ md = @regex.match(string)
110
+ self.create_enc_token(string, :block, md[2], md[3], md[1])
111
+ end
112
+ end
113
+
114
+ class DecStringTokenType < TokenType
115
+ def initialize
116
+ @regex = /DEC(\(\d+\))?::(\w+)\[(.+?)\]\!/
117
+ end
118
+ def create_token(string)
119
+ md = @regex.match(string)
120
+ EncToken.decrypted_value(:string, md[3], md[2], string, md[1])
121
+ end
122
+ end
123
+
124
+ class DecBlockTokenType < TokenType
125
+ def initialize
126
+ @regex = />\n(\s*)DEC(\(\d+\))?::(\w+)\[(.+?)\]\!/m
127
+ end
128
+ def create_token(string)
129
+ md = @regex.match(string)
130
+ EncToken.decrypted_value(:block, md[4], md[3], string, md[2], md[1])
131
+ EncToken.decrypted_value(:block, md[4], md[3], string, md[2], md[1])
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,82 @@
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 { |token_type|
45
+ s.match?(token_type.regex)
46
+ }
47
+
48
+ token =
49
+ if current_match.nil?
50
+ # No regex matches here. Find the earliest match.
51
+ next_match_indexes = @token_types.map { |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
+ }.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
+ self.parse_scanner(s) << token
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ class Hiera
2
+ module Backend
3
+ module Eyaml
4
+ module Parser
5
+ class TokenType
6
+ attr_reader :regex
7
+ @regex
8
+ def create_token string
9
+ raise 'Abstract method called'
10
+ end
11
+ end
12
+
13
+ class Token
14
+ attr_reader :match
15
+ def initialize(match)
16
+ @match = match
17
+ end
18
+ def to_encrypted(args={})
19
+ raise 'Abstract method called'
20
+ end
21
+ def to_decrypted(args={})
22
+ raise 'Abstract method called'
23
+ end
24
+ def to_plain_text
25
+ raise 'Abstract method called'
26
+ end
27
+ def to_s
28
+ "#{self.class.name}:#{@match}"
29
+ end
30
+ end
31
+
32
+ class NonMatchToken < Token
33
+ def initialize(non_match)
34
+ super(non_match)
35
+ end
36
+ def to_encrypted(args={})
37
+ @match
38
+ end
39
+ def to_decrypted(args={})
40
+ @match
41
+ end
42
+ def to_plain_text
43
+ @match
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+
3
+ class Hiera
4
+ module Backend
5
+ module Eyaml
6
+ class Plugins
7
+
8
+ @@plugins = []
9
+ @@commands = []
10
+ @@options = []
11
+
12
+ def self.register_options args
13
+ options = args[ :options ]
14
+ plugin = args[ :plugin ]
15
+ options.each do |name, option_hash|
16
+ new_option = {:name => "#{plugin}_#{name}"}
17
+ new_option.merge! option_hash
18
+ @@options << new_option
19
+ end
20
+ end
21
+
22
+ def self.options
23
+ @@options
24
+ end
25
+
26
+ def self.find
27
+
28
+ this_version = Gem::Version.create(Hiera::Backend::Eyaml::VERSION)
29
+ index = Gem::VERSION >= "1.8.0" ? Gem::Specification : Gem.source_index
30
+
31
+ [index].flatten.each do |source|
32
+ specs = Gem::VERSION >= "1.6.0" ? source.latest_specs(true) : source.latest_specs
33
+
34
+ specs.each do |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 = nil
41
+ if Gem::VERSION >= "1.8.0"
42
+ file = spec.matches_for_glob("**/eyaml_init.rb").first
43
+ else
44
+ file = Gem.searcher.matching_files(spec, "**/eyaml_init.rb").first
45
+ end
46
+
47
+ next unless file
48
+
49
+ @@plugins << spec
50
+ load file
51
+ end
52
+
53
+ end
54
+
55
+ @@plugins
56
+
57
+ end
58
+
59
+ def self.plugins
60
+ @@plugins
61
+ end
62
+
63
+ def self.commands
64
+ @@commands
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,126 @@
1
+ require 'base64'
2
+ # require 'hiera/backend/eyaml/subcommands/unknown_command'
3
+
4
+ class Hiera
5
+ module Backend
6
+ module Eyaml
7
+
8
+ class Subcommand
9
+
10
+ class << self
11
+ attr_accessor :global_options, :options, :helptext
12
+ end
13
+
14
+ @@global_options = [
15
+ {:name => :encrypt_method,
16
+ :description => "Override default encryption and decryption method (default is PKCS7)",
17
+ :short => 'n',
18
+ :default => "pkcs7"},
19
+ {:name => :version,
20
+ :description => "Show version information"},
21
+ {:name => :verbose,
22
+ :description => "Be more verbose",
23
+ :short => 'v'},
24
+ {:name => :quiet,
25
+ :description => "Be less verbose",
26
+ :short => 'q'},
27
+ {:name => :help,
28
+ :description => "Information on how to use this command",
29
+ :short => 'h'}
30
+ ]
31
+
32
+ def self.all_options
33
+ options = @@global_options.dup
34
+ options += self.options if self.options
35
+ options += Plugins.options
36
+ options
37
+ end
38
+
39
+ def self.attach_option opt
40
+ self.suboptions += opt
41
+ end
42
+
43
+ def self.find commandname = "unknown_command"
44
+ begin
45
+ require "hiera/backend/eyaml/subcommands/#{commandname.downcase}"
46
+ rescue Exception => e
47
+ require "hiera/backend/eyaml/subcommands/unknown_command"
48
+ return Hiera::Backend::Eyaml::Subcommands::UnknownCommand
49
+ end
50
+ command_module = Module.const_get('Hiera').const_get('Backend').const_get('Eyaml').const_get('Subcommands')
51
+ command_class = Utils.find_closest_class :parent_class => command_module, :class_name => commandname
52
+ command_class || Hiera::Backend::Eyaml::Subcommands::UnknownCommand
53
+ end
54
+
55
+ def self.parse
56
+
57
+ me = self
58
+
59
+ options = Trollop::options do
60
+
61
+ version "Hiera-eyaml version " + Hiera::Backend::Eyaml::VERSION.to_s
62
+ banner ["eyaml #{me.prettyname}: #{me.description}", me.helptext, "Options:"].compact.join("\n\n")
63
+
64
+ me.all_options.each do |available_option|
65
+
66
+ skeleton = {:description => "",
67
+ :short => :none}
68
+
69
+ skeleton.merge! available_option
70
+ opt skeleton[:name],
71
+ skeleton[:desc] || skeleton[:description], #legacy plugins
72
+ :short => skeleton[:short],
73
+ :default => skeleton[:default],
74
+ :type => skeleton[:type]
75
+
76
+ end
77
+
78
+ stop_on Eyaml.subcommands
79
+
80
+ end
81
+
82
+ if options[:verbose]
83
+ Hiera::Backend::Eyaml.verbosity_level += 1
84
+ end
85
+
86
+ if options[:quiet]
87
+ Hiera::Backend::Eyaml.verbosity_level = 0
88
+ end
89
+
90
+ if options[:encrypt_method]
91
+ Hiera::Backend::Eyaml.default_encryption_scheme = options[:encrypt_method]
92
+ end
93
+
94
+ options
95
+
96
+ end
97
+
98
+ def self.validate args
99
+ args
100
+ end
101
+
102
+ def self.description
103
+ "no description"
104
+ end
105
+
106
+ def self.helptext
107
+ "Usage: eyaml #{self.prettyname} [options]"
108
+ end
109
+
110
+ def self.execute
111
+ raise StandardError, "This command is not implemented yet (#{self.to_s.split('::').last})"
112
+ end
113
+
114
+ def self.prettyname
115
+ Utils.snakecase self.to_s.split('::').last
116
+ end
117
+
118
+ def self.hidden?
119
+ false
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,29 @@
1
+ require 'hiera/backend/eyaml/subcommand'
2
+
3
+ class Hiera
4
+ module Backend
5
+ module Eyaml
6
+ module Subcommands
7
+
8
+ class Createkeys < Subcommand
9
+
10
+ def self.options
11
+ []
12
+ end
13
+
14
+ def self.description
15
+ "create a set of keys with which to encrypt/decrypt eyaml data"
16
+ end
17
+
18
+ def self.execute
19
+ encryptor = Encryptor.find Eyaml.default_encryption_scheme
20
+ encryptor.create_keys
21
+ nil
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end