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