sym 2.3.0 → 2.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|