sym 2.3.0 → 2.4.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -2
- data/README.md +106 -70
- data/Rakefile +3 -2
- data/bin/sym.completion +6 -2
- data/lib/sym.rb +28 -15
- data/lib/sym/app.rb +1 -3
- data/lib/sym/app/args.rb +5 -1
- data/lib/sym/app/cli.rb +54 -6
- data/lib/sym/app/cli_slop.rb +35 -23
- data/lib/sym/app/commands/base_command.rb +12 -19
- data/lib/sym/app/commands/bash_completion.rb +19 -3
- data/lib/sym/app/commands/decrypt.rb +1 -1
- data/lib/sym/app/commands/encrypt.rb +1 -1
- data/lib/sym/app/commands/generate_key.rb +7 -3
- data/lib/sym/app/commands/keychain_add_key.rb +7 -2
- data/lib/sym/app/commands/open_editor.rb +1 -1
- data/lib/sym/app/commands/password_protect_key.rb +9 -4
- data/lib/sym/app/commands/print_key.rb +3 -3
- data/lib/sym/app/commands/show_examples.rb +25 -17
- data/lib/sym/app/commands/show_help.rb +2 -2
- data/lib/sym/app/keychain.rb +5 -0
- data/lib/sym/app/output/base.rb +3 -7
- data/lib/sym/app/output/file.rb +0 -1
- data/lib/sym/app/output/noop.rb +2 -1
- data/lib/sym/app/password/providers.rb +4 -0
- data/lib/sym/app/password/providers/memcached_provider.rb +1 -1
- data/lib/sym/app/private_key/base64_decoder.rb +1 -0
- data/lib/sym/app/private_key/decryptor.rb +1 -0
- data/lib/sym/app/private_key/detector.rb +45 -26
- data/lib/sym/app/private_key/handler.rb +20 -25
- data/lib/sym/app/private_key/key_source_check.rb +89 -0
- data/lib/sym/application.rb +115 -33
- data/lib/sym/configuration.rb +1 -0
- data/lib/sym/constants.rb +24 -0
- data/lib/sym/data/decoder.rb +2 -1
- data/lib/sym/data/wrapper_struct.rb +1 -1
- data/lib/sym/extensions/stdlib.rb +1 -0
- data/lib/sym/version.rb +1 -1
- data/sym.gemspec +1 -1
- metadata +5 -4
- data/lib/sym/encrypted_file.rb +0 -34
@@ -5,16 +5,21 @@ module Sym
|
|
5
5
|
module Commands
|
6
6
|
class PasswordProtectKey < BaseCommand
|
7
7
|
|
8
|
-
required_options
|
9
|
-
|
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
|
-
|
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
|
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 :
|
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
|
19
|
-
command: 'sym -
|
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
|
24
|
-
command: 'sym -
|
25
|
-
|
26
|
-
|
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: '
|
29
|
-
command: 'sym -
|
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
|
33
|
-
command: 'sym -
|
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 -
|
44
|
+
command: 'sym -ck production.key -df secrets.yml.enc')
|
37
45
|
|
38
|
-
output << example(comment: 'edit an encrypted file in $EDITOR,
|
39
|
-
command: 'sym -
|
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 -
|
61
|
+
command: 'sym -gpcx staging.key')
|
54
62
|
|
55
63
|
output << example(comment: 'use the new key to encrypt a file:',
|
56
|
-
command: 'sym -
|
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 -
|
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.
|
7
|
+
required_options :help, ->(opts) { opts.keys.all? { |k| !opts[k] } }
|
8
8
|
|
9
9
|
def execute
|
10
|
-
|
10
|
+
opts_original.to_s(prefix: ' ' * 2)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/sym/app/keychain.rb
CHANGED
@@ -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)
|
data/lib/sym/app/output/base.rb
CHANGED
data/lib/sym/app/output/file.rb
CHANGED
data/lib/sym/app/output/noop.rb
CHANGED
@@ -11,7 +11,7 @@ module Sym
|
|
11
11
|
|
12
12
|
def initialize(**opts)
|
13
13
|
# disable logging
|
14
|
-
Dalli.logger = Sym::
|
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,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
|
5
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
26
|
-
->(*, detector) { detector.input_handler.prompt('Please paste your private key: ', :magenta) }
|
36
|
+
private
|
27
37
|
|
28
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|