sym 2.3.0 → 2.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -2
  3. data/README.md +106 -70
  4. data/Rakefile +3 -2
  5. data/bin/sym.completion +6 -2
  6. data/lib/sym.rb +28 -15
  7. data/lib/sym/app.rb +1 -3
  8. data/lib/sym/app/args.rb +5 -1
  9. data/lib/sym/app/cli.rb +54 -6
  10. data/lib/sym/app/cli_slop.rb +35 -23
  11. data/lib/sym/app/commands/base_command.rb +12 -19
  12. data/lib/sym/app/commands/bash_completion.rb +19 -3
  13. data/lib/sym/app/commands/decrypt.rb +1 -1
  14. data/lib/sym/app/commands/encrypt.rb +1 -1
  15. data/lib/sym/app/commands/generate_key.rb +7 -3
  16. data/lib/sym/app/commands/keychain_add_key.rb +7 -2
  17. data/lib/sym/app/commands/open_editor.rb +1 -1
  18. data/lib/sym/app/commands/password_protect_key.rb +9 -4
  19. data/lib/sym/app/commands/print_key.rb +3 -3
  20. data/lib/sym/app/commands/show_examples.rb +25 -17
  21. data/lib/sym/app/commands/show_help.rb +2 -2
  22. data/lib/sym/app/keychain.rb +5 -0
  23. data/lib/sym/app/output/base.rb +3 -7
  24. data/lib/sym/app/output/file.rb +0 -1
  25. data/lib/sym/app/output/noop.rb +2 -1
  26. data/lib/sym/app/password/providers.rb +4 -0
  27. data/lib/sym/app/password/providers/memcached_provider.rb +1 -1
  28. data/lib/sym/app/private_key/base64_decoder.rb +1 -0
  29. data/lib/sym/app/private_key/decryptor.rb +1 -0
  30. data/lib/sym/app/private_key/detector.rb +45 -26
  31. data/lib/sym/app/private_key/handler.rb +20 -25
  32. data/lib/sym/app/private_key/key_source_check.rb +89 -0
  33. data/lib/sym/application.rb +115 -33
  34. data/lib/sym/configuration.rb +1 -0
  35. data/lib/sym/constants.rb +24 -0
  36. data/lib/sym/data/decoder.rb +2 -1
  37. data/lib/sym/data/wrapper_struct.rb +1 -1
  38. data/lib/sym/extensions/stdlib.rb +1 -0
  39. data/lib/sym/version.rb +1 -1
  40. data/sym.gemspec +1 -1
  41. metadata +5 -4
  42. data/lib/sym/encrypted_file.rb +0 -34
@@ -10,7 +10,7 @@ module Sym
10
10
  class OpenEditor < BaseCommand
11
11
  include Sym
12
12
 
13
- required_options [ :private_key, :keyfile, :keychain, :interactive ],
13
+ required_options [ :key, :interactive ],
14
14
  :edit,
15
15
  :file
16
16
 
@@ -5,16 +5,21 @@ module Sym
5
5
  module Commands
6
6
  class PasswordProtectKey < BaseCommand
7
7
 
8
- required_options [:private_key, :keyfile, :keychain, :interactive],
9
- :password
10
-
8
+ required_options %i(key interactive), :password
9
+ incompatible_options %i(examples help version bash_completion)
11
10
  try_after :generate_key, :encrypt, :decrypt
12
11
 
13
12
  def execute
14
13
  retries ||= 0
15
14
 
16
15
  the_key = self.key
17
- the_key = encrypt_password_if_needed(the_key)
16
+
17
+ if opts[:password]
18
+ encrypted_key, password = encrypt_with_password(the_key)
19
+ add_password_to_the_cache(encrypted_key, password)
20
+ the_key = encrypted_key
21
+ end
22
+
18
23
  add_to_keychain_if_needed(the_key)
19
24
 
20
25
  the_key
@@ -4,9 +4,9 @@ module Sym
4
4
  module App
5
5
  module Commands
6
6
  class PrintKey < BaseCommand
7
- required_options [ :keychain, :keyfile ]
8
-
9
- try_after :generate_key, :encrypt, :decrypt, :password_protect_key, :keychain_add_key
7
+ required_options %i(keychain key)
8
+ incompatible_options %i(examples help version bash_completion)
9
+ try_after :show_examples, :generate_key, :encrypt, :decrypt, :password_protect_key, :keychain_add_key
10
10
 
11
11
  def execute
12
12
  self.key
@@ -5,7 +5,7 @@ module Sym
5
5
  module Commands
6
6
  class ShowExamples < BaseCommand
7
7
  required_options :examples
8
- try_after :show_help
8
+ try_after :show_version
9
9
 
10
10
  def execute
11
11
  output = []
@@ -15,28 +15,36 @@ module Sym
15
15
  echo: 'echo $mykey',
16
16
  result: '75ngenJpB6zL47/8Wo7Ne6JN1pnOsqNEcIqblItpfg4='.green)
17
17
 
18
- output << example(comment: 'generate a new password-protected key & save to a file',
19
- command: 'sym -gp -o ~/.key',
18
+ output << example(comment: 'generate a new key with a cached password & save to the default key file',
19
+ command: 'sym -gcpqo ' + Sym.default_key_file,
20
20
  echo: 'New Password : ' + '••••••••••'.green,
21
21
  result: 'Confirm Password : ' + '••••••••••'.green)
22
22
 
23
- output << example(comment: 'encrypt a plain text string with a key, and save the output to a file',
24
- command: 'sym -e -s ' + '"secret string"'.bold.yellow + ' -k $mykey -o file.enc',
25
- echo: 'cat file.enc',
26
- result: 'Y09MNDUyczU1S0UvelgrLzV0RTYxZz09CkBDMEw4Q0R0TmpnTm9md1QwNUNy%T013PT0K'.green)
23
+ output << example(comment: 'encrypt a plain text string with default key file, and immediately decrypt it',
24
+ command: 'sym -es ' + '"secret string"'.bold.yellow + ' | sym -d',
25
+ result: 'secret string'.green)
26
+
27
+ output << example(comment: 'encrypt secrets file using key in the environment, and --negate option:',
28
+ command: 'export PRIVATE_KEY="75ngenJpB6zL47/8Wo7Ne6JN1pnOsqNEcIqblItpfg4="',
29
+ echo: 'sym -ck PRIVATE_KEY -n secrets.yml',
30
+ result: ''.green)
27
31
 
28
- output << example(comment: 'decrypt a previously encrypted string:',
29
- command: 'sym -d -s $(cat file.enc) -k $mykey',
32
+ output << example(comment: 'encrypt a secrets file using the key in the keychain:',
33
+ command: 'sym -gqx keychain.key',
34
+ echo: 'sym -ck keychain.key -n secrets.yml',
30
35
  result: 'secret string'.green)
31
36
 
32
- output << example(comment: 'encrypt sym.yml and save it to sym.enc:',
33
- command: 'sym -e -f sym.yml -o sym.enc -k $mykey')
37
+ output << example(comment: 'encrypt/decrypt sym.yml using the default key file',
38
+ command: 'sym -gcq > ' + Sym.default_key_file,
39
+ echo: 'sym -n secrets.yml',
40
+ result: 'sym -df secrets.yml.enc',
41
+ )
34
42
 
35
43
  output << example(comment: 'decrypt an encrypted file and print it to STDOUT:',
36
- command: 'sym -df sym.enc -k $mykey')
44
+ command: 'sym -ck production.key -df secrets.yml.enc')
37
45
 
38
- output << example(comment: 'edit an encrypted file in $EDITOR, ask for key, create file backup',
39
- command: 'sym -tibf ecrets.enc',
46
+ output << example(comment: 'edit an encrypted file in $EDITOR, use default key file, create file backup',
47
+ command: 'sym -tbf secrets.enc',
40
48
  result: '
41
49
  Private Key: ••••••••••••••••••••••••••••••••••••••••••••
42
50
  Saved encrypted content to sym.enc.
@@ -50,13 +58,13 @@ Diff:
50
58
 
51
59
  if Sym::App.is_osx?
52
60
  output << example(comment: 'generate a new password-encrypted key, save it to your Keychain:',
53
- command: 'sym -gpx mykey -o ~/.key')
61
+ command: 'sym -gpcx staging.key')
54
62
 
55
63
  output << example(comment: 'use the new key to encrypt a file:',
56
- command: 'sym -x mykey -e -f password.txt -o passwords.enc')
64
+ command: 'sym -e -c -k staging.key -n etc/passwords.enc')
57
65
 
58
66
  output << example(comment: 'use the new key to inline-edit the encrypted file:',
59
- command: 'sym -x mykey -t -f sym.yml')
67
+ command: 'sym -k mykey -tf sym.yml.enc')
60
68
  end
61
69
 
62
70
  output.flatten.compact.join("\n")
@@ -4,10 +4,10 @@ module Sym
4
4
  module Commands
5
5
  class ShowHelp < BaseCommand
6
6
 
7
- required_options :help, ->(opts) { opts.to_hash.keys.all? { |k| !opts[k] } }
7
+ required_options :help, ->(opts) { opts.keys.all? { |k| !opts[k] } }
8
8
 
9
9
  def execute
10
- opts.to_s(prefix: ' ' * 2)
10
+ opts_original.to_s(prefix: ' ' * 2)
11
11
  end
12
12
  end
13
13
  end
@@ -13,6 +13,10 @@ module Sym
13
13
  class << self
14
14
  attr_accessor :user, :kind, :sub_section
15
15
 
16
+ def get(value)
17
+ self.new(value).find
18
+ end
19
+
16
20
  def configure
17
21
  yield self
18
22
  end
@@ -35,6 +39,7 @@ module Sym
35
39
  self.key_name = key_name
36
40
  self.opts = opts
37
41
  self.class.validate!
42
+ stderr_off
38
43
  end
39
44
 
40
45
  def add(password)
@@ -4,14 +4,10 @@ module Sym
4
4
  module Output
5
5
  class Base
6
6
 
7
- attr_accessor :cli
7
+ attr_accessor :opts
8
8
 
9
- def initialize(cli)
10
- self.cli = cli
11
- end
12
-
13
- def opts
14
- cli.opts
9
+ def initialize(opts)
10
+ self.opts = opts
15
11
  end
16
12
 
17
13
  @outputs = []
@@ -6,7 +6,6 @@ module Sym
6
6
 
7
7
  required_option :output
8
8
 
9
-
10
9
  def output_proc
11
10
  ->(data) {
12
11
  ::File.open(opts[:output], 'w') { |f| f.write(data) }
@@ -1,4 +1,5 @@
1
- require_relative 'base'
1
+ require 'sym/app/output/base'
2
+
2
3
  module Sym
3
4
  module App
4
5
  module Output
@@ -26,6 +26,10 @@ module Sym
26
26
  provider_from_argument(p, **opts, &block) || detect
27
27
  end
28
28
 
29
+ def provider_list
30
+ registry.keys.map(&:to_s).join(', ')
31
+ end
32
+
29
33
  private
30
34
 
31
35
  def short_name(klass)
@@ -11,7 +11,7 @@ module Sym
11
11
 
12
12
  def initialize(**opts)
13
13
  # disable logging
14
- Dalli.logger = Sym::NIL_LOGGER
14
+ Dalli.logger = Sym::Constants::Log::NIL
15
15
  self.dalli = ::Dalli::Client.new(
16
16
  * Sym::Configuration.config.password_cache_arguments[:memcached][:args],
17
17
  ** Sym::Configuration.config.password_cache_arguments[:memcached][:opts].merge!(opts)
@@ -1,3 +1,4 @@
1
+ require 'base64'
1
2
  module Sym
2
3
  module App
3
4
  module PrivateKey
@@ -1,5 +1,6 @@
1
1
  require 'sym/app/private_key/decryptor'
2
2
  require 'sym/app/password/cache'
3
+ require 'sym/errors'
3
4
  module Sym
4
5
  module App
5
6
  module PrivateKey
@@ -1,42 +1,61 @@
1
+ require 'sym/app/private_key/base64_decoder'
2
+ require 'sym/app/private_key/decryptor'
3
+ require 'sym/app/private_key/key_source_check'
4
+ require 'sym/app/input/handler'
5
+
1
6
  module Sym
2
7
  module App
3
8
  module PrivateKey
4
- class Detector < Struct.new(:opts, :input_handler) # :nodoc:s
5
- @mapping = Hash.new
6
- class << self
7
- attr_reader :mapping
9
+ class Detector < Struct.new(:opts, :input_handler, :password_cache)
10
+ attr_accessor :key, :key_source
8
11
 
9
- def register(argument, proc)
10
- self.mapping[argument] = proc
11
- end
12
+ def initialize(*args)
13
+ super(*args)
14
+ read
15
+ end
16
+
17
+ def read
18
+ return key if key
19
+ self.key, self.key_source = read!
12
20
  end
13
21
 
14
- def key
15
- self.class.mapping.each_pair do |options_key, key_proc|
16
- return key_proc.call(opts[options_key], self) if opts[options_key]
22
+ # Returns the first valid 32-bit key obtained by running the above
23
+ # procs on a given string.
24
+ def read!
25
+ KeySourceCheck::CHECKS.each do |source_check|
26
+ if result = source_check.detect(self) rescue nil
27
+ if key_ = normalize_key(result.key)
28
+ key_source_ = result.to_s
29
+ return key_, key_source_
30
+ end
31
+ end
17
32
  end
18
33
  nil
19
34
  end
20
- end
21
-
22
- Detector.register :private_key,
23
- ->(key, *) { key }
24
35
 
25
- Detector.register :interactive,
26
- ->(*, detector) { detector.input_handler.prompt('Please paste your private key: ', :magenta) }
36
+ private
27
37
 
28
- Detector.register :keychain,
29
- ->(key_name, * ) { KeyChain.new(key_name).find rescue nil }
38
+ def normalize_key(key)
39
+ return nil if key.nil?
40
+ if key && key.length > 45
41
+ key = Decryptor.new(Base64Decoder.new(key).key, input_handler, password_cache).key
42
+ end
43
+ validate(key)
44
+ end
30
45
 
31
- Detector.register :keyfile,
32
- ->(file, *) {
33
- begin
34
- ::File.read(file)
35
- rescue Errno::ENOENT
36
- raise Sym::Errors::FileNotFound.new("Encryption key file #{file} was not found.")
37
- nil
46
+ def validate(key)
47
+ if key
48
+ begin
49
+ decoded = Base64Decoder.new(key).key
50
+ decoded.length == 32 ? key : nil
51
+ rescue
52
+ nil
53
+ end
54
+ else
55
+ nil
56
+ end
38
57
  end
39
- }
58
+ end
40
59
  end
41
60
  end
42
61
  end
@@ -1,42 +1,37 @@
1
- require_relative 'detector'
2
- require_relative 'base64_decoder'
3
- require_relative 'decryptor'
4
- require_relative '../input/handler'
1
+ require 'sym/app/private_key/base64_decoder'
2
+ require 'sym/app/private_key/decryptor'
3
+ require 'sym/app/private_key/detector'
4
+ require 'sym/app/input/handler'
5
+ require 'sym/app/args'
6
+ require 'sym/errors'
5
7
  module Sym
6
8
  module App
7
9
  module PrivateKey
8
10
  # This class figures out what is the private key that is
9
11
  # provided to be used.
10
- class Handler
12
+ class Handler < Struct.new(:opts, :input_handler, :password_cache)
11
13
  include Sym
14
+ attr_accessor :key, :key_source
12
15
 
13
- attr_accessor :opts, :input_handler, :password_cache
14
- attr_writer :key
15
-
16
- def initialize(opts, input_handler, password_cache)
17
- self.opts = opts
18
- self.input_handler = input_handler
19
- self.password_cache = password_cache
16
+ def initialize(*args)
17
+ super(*args)
18
+ self.key, self.key_source = detect_key
20
19
  end
21
20
 
21
+ private
22
22
 
23
- # @return [String] key Private key detected
24
- def key
25
- return @key if @key
26
-
27
- @key = begin
28
- Detector.new(opts, input_handler).key
23
+ def detect_key
24
+ begin
25
+ reader = Detector.new(opts, input_handler, password_cache)
26
+ key = reader.key
27
+ key_source = reader.key_source
29
28
  rescue Sym::Errors::Error => e
30
- if Sym::App::Args.new(opts).specify_key? && key.nil?
29
+ if Sym::App::Args.new(opts.to_h).specify_key? && key.nil?
31
30
  raise e
32
31
  end
33
32
  end
34
-
35
- if @key && @key.length > 45
36
- @key = Decryptor.new(Base64Decoder.new(key).key, input_handler, password_cache).key
37
- end
38
-
39
- @key
33
+ key = Decryptor.new(Base64Decoder.new(key).key, input_handler, password_cache).key if key && key.length > 45
34
+ return key ? [key, key_source] : [nil, nil]
40
35
  end
41
36
  end
42
37
  end
@@ -0,0 +1,89 @@
1
+ require 'sym/app/private_key/base64_decoder'
2
+ require 'sym/app/private_key/decryptor'
3
+ require 'sym/app/input/handler'
4
+ require 'base64'
5
+
6
+ module Sym
7
+ module App
8
+ module PrivateKey
9
+ class KeySourceResult < Struct.new(:name, :reducted, :input, :key)
10
+ def to_s
11
+ "#{name}://#{reducted ? '[reducted]' : input}"
12
+ end
13
+ end
14
+
15
+ class KeySourceCheck
16
+ attr_accessor :name, :reducted, :input, :output
17
+
18
+ def initialize(name:,
19
+ reducted: false,
20
+ input: ->(detector) { detector.opts[:key] },
21
+ output:)
22
+
23
+ self.name = name
24
+ self.reducted = reducted
25
+ self.input = input
26
+ self.output = output
27
+ end
28
+
29
+ def detect(detector)
30
+ input_value = input.call(detector)
31
+ return nil unless input_value
32
+ KeySourceResult.new(name,
33
+ reducted,
34
+ input_value,
35
+ output.call(detector, input_value))
36
+ end
37
+
38
+ CHECKS = [
39
+ # Order of registration is important for auto-detect feature;
40
+ # First proc is tried before the last; first one to succeed wins.
41
+ KeySourceCheck.new(
42
+ name: :interactive,
43
+ reducted: true,
44
+ input: ->(detector) { detector.opts[:interactive] },
45
+ output: ->(detector, *) { detector.input_handler.prompt('Please paste your private key: ', :magenta) }
46
+ ),
47
+
48
+ KeySourceCheck.new(
49
+ name: :file,
50
+ output: ->(*, value) { ::File.read(value).chomp if value && File.exist?(value) }
51
+ ),
52
+
53
+ KeySourceCheck.new(
54
+ name: :keychain,
55
+ output: ->(*, value) { KeyChain.get(value) if value }
56
+ ),
57
+
58
+ KeySourceCheck.new(
59
+ name: :string,
60
+ reducted: true,
61
+ output: ->(*, value) do
62
+ decoded = begin
63
+ Base64Decoder.new(value).key
64
+ rescue
65
+ nil
66
+ end
67
+ value if decoded
68
+ end
69
+ ),
70
+
71
+ KeySourceCheck.new(
72
+ name: :env,
73
+ output: ->(*, value) { ENV[value] if value =~ /^[a-zA-Z0-9_]+$/ }
74
+ ),
75
+
76
+ KeySourceCheck.new(
77
+ name: :default_file,
78
+ input: ->(*) { Sym.default_key_file if Sym.default_key? },
79
+ output: ->(detector, *) {
80
+ key_provided_by = %i(key interactive) &
81
+ detector.opts.to_hash.keys.select { |k| detector.opts[k] }
82
+ Sym.default_key if key_provided_by.empty?
83
+ }
84
+ )
85
+ ]
86
+ end
87
+ end
88
+ end
89
+ end