encrypted_credentials 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 46ebb3c54baa7d63c62cd79b34acd6ddc8756d9b025e57d942d75467cb19ad6a
4
+ data.tar.gz: c34ffac0161c1574283378525c5202b12c83347578553ebd71cfc6ea73f9c861
5
+ SHA512:
6
+ metadata.gz: 9c646a6dfc693c9334e24b4341e44e5c39e61a05e4a7ab1619a5391aa6e7ceb0725facd5bbfbbf5a2a905c8c4c64c22554c4f9e60b99fbc95213423dec5bf0f3
7
+ data.tar.gz: 31b2f1481798a054c36916f06a223bd49227c0097bf741b43bf02f07d4a0181449c6938834c64502da4871d9a472a7fa2df59b563dc10c19850780bbaba8bf3a
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in encrypted_credentials.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ encrypted_credentials (0.1.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (13.0.3)
11
+ rspec (3.9.0)
12
+ rspec-core (~> 3.9.0)
13
+ rspec-expectations (~> 3.9.0)
14
+ rspec-mocks (~> 3.9.0)
15
+ rspec-core (3.9.2)
16
+ rspec-support (~> 3.9.3)
17
+ rspec-expectations (3.9.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.9.0)
20
+ rspec-mocks (3.9.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.9.0)
23
+ rspec-support (3.9.3)
24
+
25
+ PLATFORMS
26
+ ruby
27
+ x86_64-linux
28
+
29
+ DEPENDENCIES
30
+ encrypted_credentials!
31
+ rake (~> 13.0)
32
+ rspec (~> 3.0)
33
+
34
+ BUNDLED WITH
35
+ 2.2.19
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Eugene Zolotarev & Hodlex Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Encrypted credentials
2
+
3
+ If you ever wanted to:
4
+
5
+ ✅ edit Rails [encrypted credentials](https://guides.rubyonrails.org/security.html#environmental-security) without a need to run Rails environment (e.g. from host machine inside a docker volume, from system user which doesn't have Rails installed, etc.)
6
+
7
+ ✅ employ Rails-compatible encrypted credentials in your non-Rails Ruby app
8
+
9
+ ...this gem is for you.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'encrypted_credentials'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle install
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install encrypted_credentials
26
+
27
+ ## Usage (CLI)
28
+
29
+ ```
30
+ # To open EDITOR (nano by default) for 'development.yml.enc', using 'development.key' as key:
31
+ edit_credentials -i config/credentials/development.yml.enc
32
+
33
+ # To open EDITOR (nano by default) for 'credentials.yml.enc', using 'master.key' as key:
34
+ edit_credentials -i config/credentials.yml.enc
35
+ ```
36
+
37
+ Run `edit_credentials --help` for full list of options.
38
+
39
+ ## Usage (Ruby)
40
+
41
+ ```
42
+ require 'encrypted_credentials/coder'
43
+
44
+ # Decrypt
45
+
46
+ key_hex = "9b3821b4116cec2f1db839151eaf18bb"
47
+ data = "gf3IRwit9tIvWtaa+Ytf7ulImIyIooOH+w==--2YttW0Yus3vxR9I+--ytgdvdU9L3CnTvCSqzFzuw=="
48
+
49
+ EncryptedCredentials::Coder.decrypt(data, key_hex) #=> "some_key: some_value\n"
50
+
51
+ # Encrypt
52
+
53
+ key_hex = EncryptedCredentials::Coder.generate_key_hex
54
+ rails_compatible_encrypted_data = EncryptedCredentials::Coder.encrypt("some_key: some_value", key_hex, true)
55
+ File.write('master.key', key_hex)
56
+ File.write('credentials.yml.enc', rails_compatible_encrypted_data)
57
+ ```
58
+
59
+ ## Contributing
60
+
61
+ Bug reports and pull requests are welcome on GitHub at https://github.com/EugZol/credentials.
62
+
63
+ ## License
64
+
65
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "encrypted_credentials"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/encrypted_credentials/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "encrypted_credentials"
7
+ spec.version = EncryptedCredentials::VERSION
8
+ spec.authors = ["Eugene Zolotarev", "Hodlex Ltd."]
9
+ spec.email = ["eugzol@gmail.com", "cto@hodlhodl.com"]
10
+
11
+ spec.summary = "Light weight credentials encoder/decoder, compatible with Rails Credentials"
12
+ spec.homepage = "https://github.com/EugZol/enrypted_credentials"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.4.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optionparser'
4
+
5
+ require_relative '../lib/encrypted_credentials/coder'
6
+
7
+ options = {}
8
+
9
+ options[:in] = '/dev/stdin'
10
+ options[:out] = '/dev/stdout'
11
+
12
+ parser =
13
+ OptionParser.new do |x|
14
+ x.banner = "Usage: decrypt_credentials [options]"
15
+ x.separator ""
16
+ x.separator "Either -k or -f is required. Other options are optional."
17
+ x.separator ""
18
+
19
+ x.on('-i', '--in FILENAME', 'File with data to decrypt (omit to use STDIN)') { |o| options[:in] = o }
20
+ x.on('-o', '--out FILENAME', 'File to write decrypted data (omit to use STDOUT)') { |o| options[:out] = o }
21
+ x.on('-k', '--key KEY_HEX', 'Key (hex representation)') { |o| options[:key] = o }
22
+ x.on('-f', '--keyfile KEY_FILE', 'Key file (with hex representation of the key)') { |o| options[:key] = File.read(o) }
23
+ end
24
+
25
+ parser.parse!
26
+
27
+ key_hex = options[:key]
28
+ abort "Specify either -f or -k. See #{$0} --help" unless key_hex
29
+
30
+ data_bin = File.binread(options[:in])
31
+
32
+ File.write(options[:out], EncryptedCredentials::Coder.decrypt(data_bin, key_hex))
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optionparser'
4
+ require 'tempfile'
5
+ require 'pathname'
6
+ require 'shellwords'
7
+ require 'securerandom'
8
+
9
+ require_relative '../lib/encrypted_credentials/coder'
10
+
11
+ options = {}
12
+ options[:in] = '/dev/stdin'
13
+
14
+ parser =
15
+ OptionParser.new do |x|
16
+ x.banner = "Usage: EDITOR=nano edit_credentials [options]"
17
+ x.separator ""
18
+ x.separator "Only -i is required."
19
+ x.separator ""
20
+ x.separator "If neither -k nor -f is provided key filename will be guessed from input filename (master.key or <environment>.key)."
21
+ x.separator "If guessed key file is not found, it will be created with a random key."
22
+ x.separator ""
23
+
24
+ x.on('-i', '--in FILENAME', 'Encrypted credentials file (omit to use STDIN/STDOUT)') { |o| options[:in] = o }
25
+ x.on('-k', '--key KEY_HEX', 'Key (hex representation)') { |o| options[:key] = o }
26
+ x.on('-f', '--keyfile KEY_FILE', 'Key file (with hex representation of the key)') { |o| options[:key] = File.read(o) }
27
+ end
28
+
29
+ parser.parse!
30
+
31
+ if options[:in] == '/dev/stdin'
32
+ options[:in] = '/dev/stdin'
33
+ options[:out] = '/dev/stdout'
34
+ else
35
+ options[:out] = options[:in]
36
+ end
37
+
38
+ editor = ENV['EDITOR'] || 'nano'
39
+
40
+ data_bin = File.read(options[:in]) if File.exists?(options[:in])
41
+
42
+ key_hex = options[:key]
43
+ key_hex ||= begin
44
+ credentials_path = File.dirname(options[:in])
45
+
46
+ # credentials.yml.enc -> master.key
47
+ master_key = credentials_path + '/master.key'
48
+ # development.yml.enc -> development.key
49
+ environment_key = credentials_path + '/' + File.basename(options[:in]).split('.').first + '.key'
50
+
51
+ if File.exists?(master_key)
52
+ File.read(master_key)
53
+ elsif File.exists?(environment_key)
54
+ File.read(environment_key)
55
+ else
56
+ puts "Could not find key file (checked: #{master_key}, #{environment_key})"
57
+ key_file =
58
+ if File.basename(options[:in]).split('.').first == 'credentials'
59
+ master_key
60
+ else
61
+ environment_key
62
+ end
63
+ puts "Creating #{key_file}"
64
+ key = SecureRandom.hex(16)
65
+ File.write(key_file, key)
66
+ key
67
+ end
68
+ end
69
+
70
+ begin
71
+ tmp_file = "#{Process.pid}.#{File.basename(options[:in]).chomp('.enc')}"
72
+ tmp_path = Pathname.new(File.join(Dir.tmpdir, tmp_file))
73
+
74
+ decrypted_data = EncryptedCredentials::Coder.decrypt(data_bin, key_hex) if data_bin
75
+ tmp_path.binwrite decrypted_data
76
+
77
+ begin
78
+ system("#{editor} #{Shellwords.escape(tmp_path)}")
79
+ rescue Interrupt
80
+ puts "Interrupted. Nothing was changed."
81
+ exit
82
+ end
83
+
84
+ updated_decrypted_data = tmp_path.binread
85
+
86
+ if updated_decrypted_data == decrypted_data
87
+ puts "No changes were made, credentials file was not updated"
88
+ else
89
+ File.write(options[:out], EncryptedCredentials::Coder.encrypt(updated_decrypted_data, key_hex))
90
+ end
91
+ ensure
92
+ FileUtils.rm(tmp_path) if tmp_path&.exist?
93
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optionparser'
4
+
5
+ require_relative '../lib/encrypted_credentials/coder'
6
+
7
+ options = {}
8
+
9
+ options[:in] = '/dev/stdin'
10
+ options[:out] = '/dev/stdout'
11
+
12
+ parser =
13
+ OptionParser.new do |x|
14
+ x.banner = "Usage: encrypt_credentials [options]"
15
+ x.separator ""
16
+ x.separator "Either -k or -f is required. Other options are optional."
17
+ x.separator ""
18
+
19
+ x.on('-i', '--in FILENAME', 'File with data to encrypt (omit to use STDIN)') { |o| options[:in] = o }
20
+ x.on('-o', '--out FILENAME', 'File to write encrypted data (omit to use STDOUT)') { |o| options[:out] = o }
21
+ x.on('-k', '--key KEY_HEX', 'Key (hex representation)') { |o| options[:key] = o }
22
+ x.on('-f', '--keyfile KEY_FILE', 'Key file (with hex representation of the key)') { |o| options[:key] = File.read(o) }
23
+ end
24
+
25
+ parser.parse!
26
+
27
+ key_hex = options[:key]
28
+ abort "Specify either -f or -k. See #{$0} --help" unless key_hex
29
+
30
+ data_bin = File.binread(options[:in])
31
+
32
+ File.write(options[:out], EncryptedCredentials::Coder.encrypt(data_bin, key_hex))
@@ -0,0 +1,86 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'securerandom'
4
+
5
+ # References:
6
+ # - https://github.com/rails/rails/blob/main/activesupport/lib/active_support/encrypted_file.rb
7
+ # - https://github.com/rails/rails/blob/b71a9ccce04ac08e159d4a21de91a8d76f13d8d0/activesupport/lib/active_support/message_encryptor.rb#L147
8
+ # - https://github.com/rails/rails/blob/174ee7bb602f63e7a5c44ec52e6592f3a5dd10b1/activesupport/lib/active_support/message_encryptor.rb#L163
9
+
10
+ module EncryptedCredentials
11
+ class Coder
12
+ def self.decrypt(data_bin, key_hex)
13
+ encrypted_data, iv, auth_tag = data_bin.split("--").map { |v| Base64.strict_decode64(v) }
14
+ key = [key_hex].pack('H*')
15
+
16
+ cipher_type =
17
+ case key.bytes.length
18
+ when 16
19
+ 'aes-128-gcm'
20
+ when 32
21
+ 'aes-256-gcm'
22
+ else
23
+ raise "Wrong key length: #{key.bytes.length}"
24
+ end
25
+
26
+ raise "Unauthenticated message" if auth_tag.nil? || auth_tag.bytes.length != 16
27
+
28
+ cipher = OpenSSL::Cipher.new(cipher_type)
29
+ cipher.decrypt
30
+ cipher.key = key
31
+ cipher.iv = iv
32
+ cipher.auth_tag = auth_tag
33
+ cipher.auth_data = ""
34
+
35
+ decrypted_data = cipher.update(encrypted_data)
36
+ decrypted_data << cipher.final
37
+
38
+ if decrypted_data.bytes[0..1] == [4, 8]
39
+ Marshal.load(decrypted_data)
40
+ else
41
+ decrypted_data
42
+ end
43
+ end
44
+
45
+ def self.generate_key_hex(cipher = 'aes-128-gcm')
46
+ key_length =
47
+ case cipher
48
+ when 'aes-128-gcm'
49
+ 16
50
+ when 'aes-256-gcm'
51
+ 32
52
+ else
53
+ raise "Unsupported cipher: #{cipher}"
54
+ end
55
+
56
+ SecureRandom.hex(key_length)
57
+ end
58
+
59
+ def self.encrypt(data_bin, key_hex, use_marshal = true)
60
+ key = [key_hex].pack('H*')
61
+
62
+ cipher_type =
63
+ case key.bytes.length
64
+ when 16
65
+ 'aes-128-gcm'
66
+ when 32
67
+ 'aes-256-gcm'
68
+ else
69
+ raise "Wrong key length: #{key.bytes.length}"
70
+ end
71
+
72
+ cipher = OpenSSL::Cipher.new(cipher_type)
73
+ cipher.encrypt
74
+ cipher.key = key
75
+ iv = cipher.random_iv
76
+ cipher.auth_data = ""
77
+
78
+ data = data_bin
79
+ data = Marshal.dump(data) if use_marshal
80
+ encrypted_data = cipher.update(data)
81
+ encrypted_data << cipher.final
82
+
83
+ [encrypted_data, iv, cipher.auth_tag].map{ |x| Base64.strict_encode64(x) }.join('--')
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EncryptedCredentials
4
+ VERSION = "0.1.2"
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "encrypted_credentials/version"
4
+
5
+ module EncryptedCredentials
6
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: encrypted_credentials
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Eugene Zolotarev
8
+ - Hodlex Ltd.
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2021-12-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - eugzol@gmail.com
17
+ - cto@hodlhodl.com
18
+ executables:
19
+ - decrypt_credentials
20
+ - edit_credentials
21
+ - encrypt_credentials
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - ".gitignore"
26
+ - ".rspec"
27
+ - Gemfile
28
+ - Gemfile.lock
29
+ - LICENSE.txt
30
+ - README.md
31
+ - Rakefile
32
+ - bin/console
33
+ - bin/setup
34
+ - encrypted_credentials.gemspec
35
+ - exe/decrypt_credentials
36
+ - exe/edit_credentials
37
+ - exe/encrypt_credentials
38
+ - lib/encrypted_credentials.rb
39
+ - lib/encrypted_credentials/coder.rb
40
+ - lib/encrypted_credentials/version.rb
41
+ homepage: https://github.com/EugZol/enrypted_credentials
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ homepage_uri: https://github.com/EugZol/enrypted_credentials
46
+ source_code_uri: https://github.com/EugZol/enrypted_credentials
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 2.4.0
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.1.6
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Light weight credentials encoder/decoder, compatible with Rails Credentials
66
+ test_files: []