sym 2.8.1 → 3.0.0
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 +5 -5
- data/.circleci/config.yml +31 -30
- data/.envrc +7 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +150 -928
- data/.travis.yml +16 -26
- data/CHANGELOG.md +220 -167
- data/Gemfile +1 -0
- data/LICENSE +2 -2
- data/README.adoc +670 -0
- data/Rakefile +10 -4
- data/bin/changelog +34 -0
- data/bin/sym.completion.bash +6 -4
- data/bin/sym.symit.bash +412 -187
- data/codecov.yml +29 -0
- data/design/sym-class-dependency-future-refactor.png +0 -0
- data/design/sym-class-dependency-vertical.png +0 -0
- data/design/sym-class-dependency.graffle +0 -0
- data/design/sym-class-dependency.png +0 -0
- data/design/sym-help.png +0 -0
- data/exe/keychain +1 -1
- data/exe/sym +5 -2
- data/lib/ruby_warnings.rb +7 -0
- data/lib/sym.rb +2 -8
- data/lib/sym/app.rb +1 -2
- data/lib/sym/app/args.rb +3 -2
- data/lib/sym/app/cli.rb +34 -21
- data/lib/sym/app/cli_slop.rb +9 -2
- data/lib/sym/app/commands.rb +1 -1
- data/lib/sym/app/commands/base_command.rb +1 -1
- data/lib/sym/app/commands/bash_completion.rb +2 -2
- data/lib/sym/app/commands/open_editor.rb +1 -1
- data/lib/sym/app/commands/password_protect_key.rb +4 -4
- data/lib/sym/app/commands/show_examples.rb +1 -1
- data/lib/sym/app/input/handler.rb +7 -1
- data/lib/sym/app/keychain.rb +15 -9
- data/lib/sym/app/output/noop.rb +2 -1
- data/lib/sym/app/password/cache.rb +1 -1
- data/lib/sym/app/password/providers.rb +2 -3
- data/lib/sym/app/private_key/decryptor.rb +2 -2
- data/lib/sym/app/private_key/detector.rb +4 -7
- data/lib/sym/application.rb +6 -11
- data/lib/sym/constants.rb +39 -23
- data/lib/sym/data/wrapper_struct.rb +20 -12
- data/lib/sym/errors.rb +13 -2
- data/lib/sym/extensions/instance_methods.rb +7 -8
- data/lib/sym/extensions/stdlib.rb +0 -1
- data/lib/sym/extensions/with_retry.rb +1 -1
- data/lib/sym/extensions/with_timeout.rb +1 -1
- data/lib/sym/version.rb +54 -5
- data/sym.gemspec +36 -35
- metadata +102 -66
- data/.codeclimate.yml +0 -30
- data/README.md +0 -623
- data/lib/sym/app/password/providers/drb_provider.rb +0 -41
@@ -15,7 +15,7 @@ module Sym
|
|
15
15
|
self.providers << provider_class
|
16
16
|
end
|
17
17
|
|
18
|
-
# Detect first instance
|
18
|
+
# Detect first instance tht is "alive?" and return it.
|
19
19
|
def detect
|
20
20
|
self.detected ||= self.providers.inject(nil) do |instance, provider_class|
|
21
21
|
instance || (p = provider_class.new; p.alive? ? p : nil)
|
@@ -38,7 +38,7 @@ module Sym
|
|
38
38
|
|
39
39
|
def provider_from_argument(p, **opts, &block)
|
40
40
|
case p
|
41
|
-
|
41
|
+
when String, Symbol
|
42
42
|
provider_class_name = "#{p.to_s.capitalize}Provider"
|
43
43
|
Sym::App::Password::Providers.const_defined?(provider_class_name) ?
|
44
44
|
Sym::App::Password::Providers.const_get(provider_class_name).new(**opts, &block) :
|
@@ -53,4 +53,3 @@ end
|
|
53
53
|
|
54
54
|
# Order is important — they are tried in this order for auto detect
|
55
55
|
require 'sym/app/password/providers/memcached_provider'
|
56
|
-
require 'sym/app/password/providers/drb_provider'
|
@@ -31,10 +31,10 @@ module Sym
|
|
31
31
|
rescue ::OpenSSL::Cipher::CipherError => e
|
32
32
|
input_handler.puts 'Invalid password. Please try again.'
|
33
33
|
|
34
|
-
if (
|
34
|
+
if (retries += 1) < 3
|
35
35
|
retry
|
36
36
|
else
|
37
|
-
raise(Sym::Errors::
|
37
|
+
raise(Sym::Errors::WrongPasswordForKey.new('Invalid password.'))
|
38
38
|
end
|
39
39
|
end
|
40
40
|
else
|
@@ -23,11 +23,10 @@ module Sym
|
|
23
23
|
# procs on a given string.
|
24
24
|
def read!
|
25
25
|
KeySourceCheck::CHECKS.each do |source_check|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
26
|
+
next unless 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_
|
31
30
|
end
|
32
31
|
end
|
33
32
|
nil
|
@@ -51,8 +50,6 @@ module Sym
|
|
51
50
|
rescue
|
52
51
|
nil
|
53
52
|
end
|
54
|
-
else
|
55
|
-
nil
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|
data/lib/sym/application.rb
CHANGED
@@ -32,7 +32,6 @@ module Sym
|
|
32
32
|
:stdin, :stdout, :stderr, :kernel
|
33
33
|
|
34
34
|
def initialize(opts, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = nil)
|
35
|
-
|
36
35
|
self.stdin = stdin
|
37
36
|
self.stdout = stdout
|
38
37
|
self.stderr = stderr
|
@@ -111,16 +110,12 @@ module Sym
|
|
111
110
|
end
|
112
111
|
|
113
112
|
def editor
|
114
|
-
editors_to_try.find { |editor| File.exist?(editor) }
|
113
|
+
editors_to_try.compact.find { |editor| File.exist?(editor) }
|
115
114
|
end
|
116
115
|
|
117
116
|
def process_output(result)
|
118
|
-
unless result.is_a?(Hash)
|
119
|
-
|
120
|
-
result
|
121
|
-
else
|
122
|
-
result
|
123
|
-
end
|
117
|
+
self.output.call(result) unless result.is_a?(Hash)
|
118
|
+
result
|
124
119
|
end
|
125
120
|
|
126
121
|
private
|
@@ -182,7 +177,7 @@ module Sym
|
|
182
177
|
args[:verbose] = opts[:verbose]
|
183
178
|
args[:provider] = opts[:cache_provider] if opts[:cache_provider]
|
184
179
|
|
185
|
-
self.password_cache = Sym::App::Password::Cache.instance.configure(args)
|
180
|
+
self.password_cache = Sym::App::Password::Cache.instance.configure(**args)
|
186
181
|
end
|
187
182
|
|
188
183
|
def process_edit_option
|
@@ -207,7 +202,7 @@ module Sym
|
|
207
202
|
end
|
208
203
|
|
209
204
|
def initialize_action
|
210
|
-
self.action = if opts[:encrypt]
|
205
|
+
self.action = if opts[:encrypt]
|
211
206
|
:encr
|
212
207
|
elsif opts[:decrypt]
|
213
208
|
:decr
|
@@ -217,7 +212,7 @@ module Sym
|
|
217
212
|
# If we are encrypting or decrypting, and no data has been provided, check if we
|
218
213
|
# should read from STDIN
|
219
214
|
def initialize_data_source
|
220
|
-
if self.action && opts[:string].nil? && opts[:file].nil? && !
|
215
|
+
if self.action && opts[:string].nil? && opts[:file].nil? && !self.stdin.tty?
|
221
216
|
opts[:file] = '-'
|
222
217
|
end
|
223
218
|
end
|
data/lib/sym/constants.rb
CHANGED
@@ -1,43 +1,59 @@
|
|
1
1
|
require 'logger'
|
2
2
|
module Sym
|
3
|
+
#
|
4
|
+
# This module is responsible for installing Sym BASH extensions.
|
5
|
+
#
|
3
6
|
module Constants
|
4
|
-
module Bash
|
5
7
|
|
6
|
-
|
8
|
+
BASH_FILES = Dir.glob("#{File.expand_path('../../bin', __dir__)}/sym.*.bash").freeze
|
7
9
|
|
8
|
-
|
10
|
+
class << self
|
11
|
+
attr_reader :user_home
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
13
|
+
def user_home=(value)
|
14
|
+
@user_home = value
|
15
|
+
register_bash_files!
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
+
def config
|
19
|
+
@config ||= {}
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
def sym_key_file
|
23
|
+
"#{user_home}/.sym.key"
|
24
|
+
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
script: "[[ -f #{home_file} ]] && source #{home_file}"
|
27
|
-
}
|
26
|
+
def register_bash_files!
|
27
|
+
BASH_FILES.each do |bash_file|
|
28
|
+
register_bash_extension bash_file
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
private
|
33
|
+
|
34
|
+
def register_bash_extension(bash_file)
|
35
|
+
return unless user_home && Dir.exist?(user_home)
|
36
|
+
|
37
|
+
source_file = File.basename(bash_file)
|
38
|
+
home_file = "#{user_home}/.#{source_file}"
|
39
|
+
config_key = source_file.gsub(/sym\./, '').gsub(/\.bash/, '').to_sym
|
40
|
+
|
41
|
+
config[config_key] = {
|
42
|
+
dest: home_file,
|
43
|
+
source: bash_file,
|
44
|
+
script: "[[ -f #{home_file} ]] && source #{home_file}"
|
45
|
+
}
|
46
|
+
end
|
32
47
|
end
|
33
48
|
|
49
|
+
self.user_home ||= ::Dir.home rescue nil
|
50
|
+
self.user_home ||= '/tmp'
|
51
|
+
|
52
|
+
self.register_bash_files!
|
53
|
+
|
34
54
|
module Log
|
35
55
|
NIL = Logger.new(nil).freeze # empty logger
|
36
56
|
LOG = Logger.new(STDERR).freeze
|
37
57
|
end
|
38
|
-
|
39
|
-
ENV_ARGS_VARIABLE_NAME = 'SYM_ARGS'.freeze
|
40
|
-
SYM_KEY_FILE = "#{ENV['HOME']}/.sym.key"
|
41
|
-
|
42
58
|
end
|
43
59
|
end
|
@@ -2,24 +2,32 @@ require 'sym/errors'
|
|
2
2
|
module Sym
|
3
3
|
module Data
|
4
4
|
class WrapperStruct < Struct.new(
|
5
|
-
|
6
|
-
:
|
7
|
-
|
8
|
-
:
|
9
|
-
|
10
|
-
:
|
11
|
-
|
5
|
+
# [Blob] Binary encrypted data (possibly compressed)s
|
6
|
+
:encrypted_data,
|
7
|
+
# [String] IV used to encrypt the datas
|
8
|
+
:iv,
|
9
|
+
# [String] Name of the cipher used
|
10
|
+
:cipher_name,
|
11
|
+
# [Integer] For password-encrypted data this is the salt
|
12
|
+
:salt,
|
13
|
+
# [Integer] Version of the cipher used
|
14
|
+
:version,
|
15
|
+
# [Boolean] indicates if compression should be applied
|
16
|
+
:compress
|
17
|
+
)
|
18
|
+
|
19
|
+
define_singleton_method(:new, Class.method(:new))
|
12
20
|
|
13
21
|
VERSION = 1
|
14
22
|
|
15
23
|
attr_accessor :compressed
|
16
24
|
|
17
25
|
def initialize(
|
18
|
-
encrypted_data:,
|
19
|
-
iv:,
|
20
|
-
cipher_name:,
|
21
|
-
salt: nil,
|
22
|
-
version: VERSION,
|
26
|
+
encrypted_data:,
|
27
|
+
iv:,
|
28
|
+
cipher_name:,
|
29
|
+
salt: nil,
|
30
|
+
version: VERSION,
|
23
31
|
compress: Sym::Configuration.config.compression_enabled
|
24
32
|
)
|
25
33
|
super(encrypted_data, iv, cipher_name, salt, version, compress)
|
data/lib/sym/errors.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
module Sym
|
2
2
|
# All public exceptions of this library are here.
|
3
3
|
module Errors
|
4
|
+
# @formatter:off
|
4
5
|
# Exceptions superclass for this library.
|
5
|
-
class
|
6
|
+
class Error < StandardError; end
|
6
7
|
|
7
8
|
# No secret has been provided for encryption or decryption
|
8
9
|
class InsufficientOptionsError < Sym::Errors::Error; end
|
9
10
|
|
10
11
|
class PasswordError < Sym::Errors::Error; end
|
12
|
+
|
13
|
+
class InvalidSymHomeDirectory < Sym::Errors::Error; end
|
14
|
+
|
11
15
|
class NoPasswordProvided < Sym::Errors::PasswordError; end
|
16
|
+
|
12
17
|
class PasswordsDontMatch < Sym::Errors::PasswordError; end
|
18
|
+
|
13
19
|
class PasswordTooShort < Sym::Errors::PasswordError; end
|
20
|
+
|
14
21
|
class CantReadPasswordNoTTY < Sym::Errors::PasswordError; end
|
15
22
|
|
16
23
|
class EditorExitedAbnormally < Sym::Errors::Error; end
|
@@ -20,13 +27,17 @@ module Sym
|
|
20
27
|
class DataEncodingVersionMismatch< Sym::Errors::Error; end
|
21
28
|
|
22
29
|
class KeyError < Sym::Errors::Error; end
|
30
|
+
|
23
31
|
class InvalidEncodingPrivateKey < Sym::Errors::KeyError; end
|
24
|
-
|
32
|
+
|
33
|
+
class WrongPasswordForKey < Sym::Errors::KeyError; end
|
34
|
+
|
25
35
|
class NoPrivateKeyFound < Sym::Errors::KeyError; end
|
26
36
|
|
27
37
|
class NoDataProvided < Sym::Errors::Error; end
|
28
38
|
|
29
39
|
class KeyChainCommandError < Sym::Errors::Error; end
|
40
|
+
# @formatter:on
|
30
41
|
|
31
42
|
# Method was called on an abstract class. Override such methods in
|
32
43
|
# subclasses, and use subclasses for instantiation of objects.
|
@@ -71,7 +71,7 @@ module Sym
|
|
71
71
|
def make_password_key(cipher, password, salt = nil)
|
72
72
|
key_len = cipher.key_len
|
73
73
|
salt ||= OpenSSL::Random.random_bytes 16
|
74
|
-
iter =
|
74
|
+
iter = 20_000
|
75
75
|
digest = OpenSSL::Digest::SHA256.new
|
76
76
|
key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iter, key_len, digest)
|
77
77
|
return key, salt
|
@@ -87,12 +87,12 @@ module Sym
|
|
87
87
|
block.call(cipher_struct) if block
|
88
88
|
|
89
89
|
encrypted_data = update_cipher(cipher_struct.cipher, data)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
arguments = { encrypted_data: encrypted_data,
|
91
|
+
iv: cipher_struct.iv,
|
92
|
+
cipher_name: cipher_struct.cipher.name,
|
93
|
+
salt: cipher_struct.salt,
|
94
|
+
compress: !compression_enabled }
|
95
|
+
wrapper_struct = WrapperStruct.new(arguments)
|
96
96
|
encode(wrapper_struct, false)
|
97
97
|
end
|
98
98
|
|
@@ -107,7 +107,6 @@ module Sym
|
|
107
107
|
decode(update_cipher(cipher_struct.cipher, wrapper_struct.encrypted_data))
|
108
108
|
end
|
109
109
|
|
110
|
-
|
111
110
|
def encode_incoming_data(data)
|
112
111
|
compression_enabled = !data.respond_to?(:size) || (data.size > 100 && encryption_config.compression_enabled)
|
113
112
|
data = encode(data, compression_enabled)
|
data/lib/sym/version.rb
CHANGED
@@ -1,8 +1,57 @@
|
|
1
1
|
module Sym
|
2
|
-
VERSION = '
|
3
|
-
DESCRIPTION =
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
VERSION = '3.0.0'
|
3
|
+
DESCRIPTION = <<~eof
|
4
|
+
|
5
|
+
Sym is a ruby library (gem) that offers both the command line interface
|
6
|
+
(CLI) and a set of rich Ruby APIs, which make it rather trivial to add
|
7
|
+
encryption and decryption of sensitive data to your development or deployment
|
8
|
+
workflow.
|
9
|
+
|
10
|
+
For additional security the private key itself can be encrypted with a
|
11
|
+
user-generated password. For decryption using the key the password can be
|
12
|
+
input into STDIN, or be defined by an ENV variable, or an OS-X Keychain Entry.
|
13
|
+
|
14
|
+
Unlike many other existing encryption tools, Sym focuses on getting out of
|
15
|
+
your way by offering a streamlined interface with password caching (if
|
16
|
+
MemCached is installed and running locally) in hopes to make encryption of
|
17
|
+
application secrets nearly completely transparent to the developers.
|
18
|
+
|
19
|
+
Sym uses symmetric 256-bit key encryption with the AES-256-CBC cipher,
|
20
|
+
same cipher as used by the US Government.
|
21
|
+
|
22
|
+
For password-protecting the key Sym uses AES-128-CBC cipher. The resulting
|
23
|
+
data is zlib-compressed and base64-encoded. The keys are also base64 encoded
|
24
|
+
for easy copying/pasting/etc.
|
25
|
+
|
26
|
+
Sym accomplishes encryption transparency by combining several convenient features:
|
27
|
+
|
28
|
+
1. Sym can read the private key from multiple source types, such as pathname,
|
29
|
+
an environment variable name, a keychain entry, or CLI argument. You simply
|
30
|
+
pass either of these to the -k flag — one flag that works for all source types.
|
31
|
+
|
32
|
+
2. By utilizing OS-X Keychain on a Mac, Sym offers truly secure way of
|
33
|
+
storing the key on a local machine, much more secure then storing it on a file system,
|
34
|
+
|
35
|
+
3. By using a local password cache (activated with -c) via an in-memory provider
|
36
|
+
such as memcached, sym invocations take advantage of password cache, and
|
37
|
+
only ask for a password once per a configurable time period,
|
38
|
+
|
39
|
+
4. By using SYM_ARGS environment variable, where common flags can be saved. This
|
40
|
+
is activated with sym -A,
|
41
|
+
|
42
|
+
5. By reading the key from the default key source file ~/.sym.key which
|
43
|
+
requires no flags at all,
|
44
|
+
|
45
|
+
6. By utilizing the --negate option to quickly encrypt a regular file, or decrypt
|
46
|
+
an encrypted file with extension .enc
|
47
|
+
|
48
|
+
7. By implementing the -t (edit) mode, that opens an encrypted file in your $EDITOR,
|
49
|
+
and replaces the encrypted version upon save & exit, optionally creating a backup.
|
50
|
+
|
51
|
+
8. By offering the Sym::MagicFile ruby API to easily read encrypted files into memory.
|
52
|
+
|
53
|
+
Please refer the module documentation available here:
|
54
|
+
https://www.rubydoc.info/gems/sym
|
55
|
+
|
7
56
|
eof
|
8
57
|
end
|
data/sym.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'sym/version'
|
@@ -19,48 +18,50 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.bindir = 'exe'
|
20
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
20
|
spec.require_paths = ['lib']
|
22
|
-
spec.required_ruby_version = '>= 2.
|
23
|
-
spec.post_install_message =
|
24
|
-
|
25
|
-
Thank you for installing Sym!
|
26
|
-
|
27
|
-
BLOG POST
|
28
|
-
=========
|
29
|
-
http://kig.re/2017/03/10/dead-simple-encryption-with-sym.html
|
30
|
-
|
31
|
-
BASH COMPLETION
|
32
|
-
===============
|
33
|
-
To enable bash command line completion and install highly useful
|
34
|
-
command line BASH wrapper 'symit', please run the following
|
35
|
-
command after installing the gem. It appends sym's shell completion
|
36
|
-
wrapper to the file specified in arguments to -B flag.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Thank you for using Sym and happy encrypting :)
|
45
|
-
|
46
|
-
@kigster on Github,
|
47
|
-
|
48
|
-
|
49
|
-
EOF
|
21
|
+
spec.required_ruby_version = '>= 2.3'
|
22
|
+
spec.post_install_message = <<~EOF
|
23
|
+
|
24
|
+
Thank you for installing Sym!
|
25
|
+
|
26
|
+
BLOG POST
|
27
|
+
=========
|
28
|
+
http://kig.re/2017/03/10/dead-simple-encryption-with-sym.html
|
29
|
+
|
30
|
+
BASH COMPLETION
|
31
|
+
===============
|
32
|
+
To enable bash command line completion and install highly useful
|
33
|
+
command line BASH wrapper 'symit', please run the following
|
34
|
+
command after installing the gem. It appends sym's shell completion
|
35
|
+
wrapper to the file specified in arguments to -B flag.
|
36
|
+
|
37
|
+
sym -B ~/.bash_profile
|
38
|
+
source ~/.bash_profile
|
39
|
+
# then:
|
40
|
+
sym --help
|
41
|
+
symit --help
|
42
|
+
|
43
|
+
Thank you for using Sym and happy encrypting :)
|
44
|
+
|
45
|
+
@kigster on Github,
|
46
|
+
@kig on Twitter.
|
47
|
+
|
48
|
+
EOF
|
50
49
|
spec.add_dependency 'colored2', '~> 3'
|
51
50
|
spec.add_dependency 'slop', '~> 4.3'
|
52
51
|
spec.add_dependency 'activesupport'
|
53
|
-
spec.add_dependency 'highline'
|
54
|
-
spec.add_dependency '
|
55
|
-
spec.add_dependency 'dalli', '~> 2.7'
|
52
|
+
spec.add_dependency 'highline'
|
53
|
+
spec.add_dependency 'dalli'
|
56
54
|
|
57
|
-
spec.add_development_dependency '
|
58
|
-
spec.add_development_dependency 'simplecov'
|
59
|
-
spec.add_development_dependency 'irbtools'
|
55
|
+
spec.add_development_dependency 'asciidoctor'
|
60
56
|
spec.add_development_dependency 'aruba'
|
61
57
|
spec.add_development_dependency 'bundler'
|
58
|
+
spec.add_development_dependency 'irbtools'
|
62
59
|
spec.add_development_dependency 'rake'
|
60
|
+
spec.add_development_dependency 'relaxed-rubocop'
|
63
61
|
spec.add_development_dependency 'rspec', '~> 3'
|
64
62
|
spec.add_development_dependency 'rspec-its'
|
63
|
+
spec.add_development_dependency 'rubocop', '0.81.0'
|
64
|
+
spec.add_development_dependency 'simplecov'
|
65
|
+
spec.add_development_dependency 'codecov'
|
65
66
|
spec.add_development_dependency 'yard'
|
66
67
|
end
|