kms-tools 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.octopolo.yml +4 -0
- data/.soyuz.yml +13 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.markdown +7 -0
- data/Gemfile +2 -0
- data/README.md +85 -0
- data/Rakefile +14 -0
- data/bin/kms-tools +44 -0
- data/kms-tools.gemspec +29 -0
- data/lib/kms-cli.rb +2 -0
- data/lib/kms-tools.rb +5 -0
- data/lib/kms-tools/base.rb +88 -0
- data/lib/kms-tools/cli/commands/decrypt.rb +15 -0
- data/lib/kms-tools/cli/commands/encrypt.rb +16 -0
- data/lib/kms-tools/cli/commands/list_aliases.rb +13 -0
- data/lib/kms-tools/cli/decrypt.rb +55 -0
- data/lib/kms-tools/cli/encrypt.rb +62 -0
- data/lib/kms-tools/cli/helpers.rb +30 -0
- data/lib/kms-tools/cli/output.rb +61 -0
- data/lib/kms-tools/decrypter.rb +82 -0
- data/lib/kms-tools/encrypted_file.rb +184 -0
- data/lib/kms-tools/encrypter.rb +96 -0
- data/lib/kms-tools/version.rb +3 -0
- data/spec/kms-tools/aws_mocks.rb +37 -0
- data/spec/kms-tools/base_spec.rb +83 -0
- data/spec/kms-tools/decrypter_spec.rb +95 -0
- data/spec/kms-tools/encrypted_file_spec.rb +14 -0
- data/spec/kms-tools/encrypter_spec.rb +91 -0
- data/spec/kms-tools/test_data.rb +11 -0
- data/spec/spec_helper.rb +26 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 36135cba337e0583f02134a249daf299dea7ec5f
|
4
|
+
data.tar.gz: 06877816b0f9bab9a09034271c5fe2e0584c9ade
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 67c1df3ef40a95e59d1331e5628232b32ae51fe9261990f268114553eebc5b20f9c0ae47f5d2afb42b7daa2088311145633bbfb678cb2a913e7e170670db0a6a
|
7
|
+
data.tar.gz: 00083083afa0d09b9b79e71672957a36a4f6fdeacb02ec9fefbe8104a9b1a2b8ec25d6605c044a907eea2e60ce519e015ea033a5be6ab8c751a41ccb5f0055e1
|
data/.gitignore
ADDED
data/.octopolo.yml
ADDED
data/.soyuz.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
defaults:
|
2
|
+
deploy_cmd: gem push *.gem
|
3
|
+
before_deploy_cmds:
|
4
|
+
- /usr/local/bin/op tag-release
|
5
|
+
- sed -i "" -e "s/\".*/\"$(git tag| sort -n -t. -k1,1 -k2,2 -k3,3 | tail -1 | sed s/v//)\"/" lib/kms-tools/version.rb
|
6
|
+
- git add lib/kms-tools/version.rb
|
7
|
+
- git commit -m "Version Bump" && git push
|
8
|
+
- gem build kms-tools.gemspec
|
9
|
+
after_deploy_cmds:
|
10
|
+
- rm *.gem
|
11
|
+
environments:
|
12
|
+
-
|
13
|
+
rubygems: {}
|
data/.travis.yml
ADDED
data/CHANGELOG.markdown
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
## KmsTools
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.com/sportngin/kms-tools.svg?token=CZhBgnU2zo8LVq9DDxQf&branch=master)](https://travis-ci.com/sportngin/kms-tools)
|
4
|
+
|
5
|
+
A simplified toolset for encrypting and decrypting data using [Amazon Key Management Service](https://aws.amazon.com/kms/). Since credentials are managed by the [AWS SDK](https://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs), you can use a local credentials file for the CLI and rely on IAM roles to provide access for applications running on AWS instances. This completely removes the need to manage plaintext secret keys even for locally encrypted and decrypted data.
|
6
|
+
|
7
|
+
### Local Encryption
|
8
|
+
Per the KMS spec, data up to 4KB may be encryptyed with a simple `encrypt` call. Larger blobs of data must be locally encrypted using data keys. KmsTools facilitates this by generating two data keys for each blob encrypted. One is used as the symmetric secret key and the other used as the initialization vector. These two keys are encrypted and then stored along side the encrypted data for later use.
|
9
|
+
|
10
|
+
Standard lib OpenSSL is used for local encryption. While any block cipher compiled in to OpenSSL can be used for local encryption, it is important to note that the same cipher must be available on any machine attempting to decrypt the same data. So it is recommended to use commonly available ciphers. `aes-256-cbc` is the default.
|
11
|
+
|
12
|
+
### .kms Filetype
|
13
|
+
To make storage and recall of encryption metadata easier, the `.kms` filetype was developed. This filetype is used when storing encrypted files with the CLI and by default when using [KmsTools::EncryptedFile](https://github.com/sportngin/kms-tools/blob/master/lib/kms-tools/encrypted_file.rb "KmsTools::EncryptedFile"). `.kms` files are a digital envelope made up of 3 specific parts:
|
14
|
+
1. A zero-padded 7 byte integer noting total length of metadata stored
|
15
|
+
2. YAML encoded metadata
|
16
|
+
3. Encrypted binary data
|
17
|
+
```
|
18
|
+
meta-length metadata encrypted binary data
|
19
|
+
INT YAML BIN
|
20
|
+
|-----------|----------------------|---------------------------------------------------------|
|
21
|
+
```
|
22
|
+
|
23
|
+
While an arbitrary number of metadata elements can be stored as needed, there are a few standard elements
|
24
|
+
* OpenSSL cipher used for encryption `required`
|
25
|
+
* Encrypted encryption key (decypted with KMS prior to using) `required`
|
26
|
+
* Encrypted initialization vector (decypted with KMS prior to using) `required`
|
27
|
+
* Original file extension `required`
|
28
|
+
* Original data checksum `required`
|
29
|
+
* KMS Master Key ARN (Optional but there by default for troubleshooting)
|
30
|
+
|
31
|
+
|
32
|
+
### CLI
|
33
|
+
The CLI exposes most of the functionality of the gem in a simple interface.
|
34
|
+
|
35
|
+
>Keep in mind that the gem uses standard AWS credential management. So if you are using the CLI on a server without a credentials file in place, all activity will be logged in CloudTrail as the role instead of the user taking action. For this reason it is highly recommended that you run some sort of IDS on instances using roles that have access to KMS to ensure that all KMS activity is properly logged.
|
36
|
+
|
37
|
+
```
|
38
|
+
NAME
|
39
|
+
kms-tools - CLI for encrypting and decrypting information with Amazon KMS
|
40
|
+
|
41
|
+
SYNOPSIS
|
42
|
+
kms-tools [global options] command [command options] [arguments...]
|
43
|
+
|
44
|
+
VERSION
|
45
|
+
0.0.1
|
46
|
+
|
47
|
+
GLOBAL OPTIONS
|
48
|
+
--[no-]color - Colorize output (default: enabled)
|
49
|
+
-d, --debug - Debug output (Includes verbose)
|
50
|
+
--help - Show this message
|
51
|
+
-k, --master_key=arg - Encrypt using the specified key alias or key id as the customer master key (default: none)
|
52
|
+
-p, --profile=arg - AWS credentials profile to use (default: default)
|
53
|
+
-r, --region=arg - AWS Region (default: us-east-1)
|
54
|
+
-v, --verbose - Verbose output
|
55
|
+
--version - Display the program version
|
56
|
+
|
57
|
+
COMMANDS
|
58
|
+
decrypt - Decrypt a text string or a file
|
59
|
+
encrypt - Encrypt a text string or a file
|
60
|
+
help - Shows a list of commands or help for one command
|
61
|
+
list-aliases - List KMS key aliases available to current credentials
|
62
|
+
```
|
63
|
+
|
64
|
+
Example string encryption/decryption:
|
65
|
+
```
|
66
|
+
[~/sportngin/kms-tools] (master)$ bundle exec bin/kms-tools encrypt "something secret"
|
67
|
+
Choose which key alias to use as the base Customer Master Key:
|
68
|
+
1. alias/staging/kms-tools1
|
69
|
+
2. alias/staging/kms-tools2
|
70
|
+
3. alias/staging/vpn
|
71
|
+
? 1
|
72
|
+
Encrypted ciphertext (copy all text without surrounding whitespace):
|
73
|
+
|
74
|
+
CiAA6CYSKxFRMav+m01ps0d8V5PrVvbPebe2L7LNbrV7NBKXAQEBAgB4AOgmEisRUTGr/ptNabNHfFeT61b2z3m3ti+yzW61ezQAAABuMGwGCSqGSIb3DQEHBqBfMF0CAQAwWAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwFjvAk2NeDhOGxlUUCARCAK1hO8B+M6RWjiQckqI4RlGnP8mI/gePSfERHcD0KgwvJwhiPP8z+p0m2TkY=
|
75
|
+
|
76
|
+
|
77
|
+
[~/sportngin/kms-tools] (master)$ bundle exec bin/kms-tools decrypt CiAA6CYSKxFRMav+m01ps0d8V5PrVvbPebe2L7LNbrV7NBKXAQEBAgB4AOgmEisRUTGr/ptNabNHfFeT61b2z3m3ti+yzW61ezQAAABuMGwGCSqGSIb3DQEHBqBfMF0CAQAwWAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwFjvAk2NeDhOGxlUUCARCAK1hO8B+M6RWjiQckqI4RlGnP8mI/gePSfERHcD0KgwvJwhiPP8z+p0m2TkY=
|
78
|
+
Decrypted string:
|
79
|
+
|
80
|
+
something secret
|
81
|
+
|
82
|
+
|
83
|
+
[~/sportngin/kms-tools] (master)$
|
84
|
+
```
|
85
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
require 'rdoc/task'
|
5
|
+
Rake::RDocTask.new do |rd|
|
6
|
+
rd.main = "README.rdoc"
|
7
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
8
|
+
rd.title = 'KMS Tools'
|
9
|
+
end
|
10
|
+
|
11
|
+
spec = eval(File.read('kms-tools.gemspec'))
|
12
|
+
|
13
|
+
Gem::PackageTask.new(spec) do |pkg|
|
14
|
+
end
|
data/bin/kms-tools
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'gli'
|
3
|
+
require 'kms-cli'
|
4
|
+
|
5
|
+
include GLI::App
|
6
|
+
|
7
|
+
program_desc 'CLI for encrypting and decrypting information with Amazon KMS'
|
8
|
+
|
9
|
+
version KmsTools::VERSION
|
10
|
+
|
11
|
+
subcommand_option_handling :normal
|
12
|
+
arguments :strict
|
13
|
+
|
14
|
+
switch [:v,:verbose], :desc => 'Verbose output', :negatable => false
|
15
|
+
switch [:d,:debug], :desc => 'Debug output (Includes verbose)', :negatable => false
|
16
|
+
switch [:color], :desc => 'Colorize output', :default_value => true
|
17
|
+
flag [:r,:region], :desc => 'AWS Region', :default_value => 'us-east-1'
|
18
|
+
flag [:p,:profile], :desc => 'AWS credentials profile to use', :default_value => 'default'
|
19
|
+
flag [:k,:master_key], :desc => 'Encrypt using the specified key alias or key id as the customer master key'
|
20
|
+
|
21
|
+
# Load all the command files
|
22
|
+
commands_from '../lib/kms-tools/cli/commands'
|
23
|
+
|
24
|
+
pre do |global,command,options,args|
|
25
|
+
#KmsTools::VerifyAwsConfig.new
|
26
|
+
$verbose = global[:verbose] || global[:debug]
|
27
|
+
$debug = global[:debug]
|
28
|
+
$color = global[:color]
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
post do |global,command,options,args|
|
33
|
+
# Post logic here
|
34
|
+
# Use skips_post before a command to skip this
|
35
|
+
# block on that command only
|
36
|
+
end
|
37
|
+
|
38
|
+
on_error do |exception|
|
39
|
+
# Error logic here
|
40
|
+
# return false to skip default error handling
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
exit run(ARGV)
|
data/kms-tools.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Ensure we require the local version and not one we might have installed already
|
2
|
+
require './lib/kms-tools/version'
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'kms-tools'
|
5
|
+
s.version = KmsTools::VERSION
|
6
|
+
s.author = 'Matt Krieger'
|
7
|
+
s.email = 'matt.krieger@sportngin.com'
|
8
|
+
s.homepage = 'http://www.sportngin.com'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'CLI for encrypting and decrypting data using Amazon KMS'
|
11
|
+
s.files = `git ls-files`.split("
|
12
|
+
")
|
13
|
+
s.require_paths << 'lib'
|
14
|
+
s.has_rdoc = 'yard'
|
15
|
+
s.bindir = 'bin'
|
16
|
+
s.executables << 'kms-tools'
|
17
|
+
|
18
|
+
s.add_dependency 'aws-sdk'
|
19
|
+
s.add_dependency "highline"
|
20
|
+
s.add_dependency "hashdiff"
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'yard'
|
24
|
+
s.add_development_dependency 'rspec-core'
|
25
|
+
s.add_development_dependency 'rspec-expectations'
|
26
|
+
s.add_development_dependency 'rspec-mocks'
|
27
|
+
|
28
|
+
s.add_runtime_dependency "gli", "~>2.13.4"
|
29
|
+
end
|
data/lib/kms-cli.rb
ADDED
data/lib/kms-tools.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module KmsTools
|
2
|
+
##
|
3
|
+
# Helper class for {http://docs.aws.amazon.com/sdkforruby/api/Aws/KMS/Client.html Aws::KMS::Client}.
|
4
|
+
class Base
|
5
|
+
# Default region if nothing is provided because we all use N. Virginia, don't we?
|
6
|
+
DEFAULT_REGION = 'us-east-1'
|
7
|
+
|
8
|
+
# Customer master key used for ann encryption operations
|
9
|
+
attr_accessor :master_key
|
10
|
+
|
11
|
+
# InstantiatedAws::KMS::Client object
|
12
|
+
attr_reader :kms
|
13
|
+
|
14
|
+
##
|
15
|
+
# Instantiates a {http://docs.aws.amazon.com/sdkforruby/api/Aws/KMS/Client.html Aws::KMS::Client} object with provided options.
|
16
|
+
#
|
17
|
+
# @param [Hash] options
|
18
|
+
# @option options [String] :master_key Customer Master Key to use when instantiating the object, this can be a full key ID, an alias, or an ARN
|
19
|
+
# @option options [String] :region Set the region for Aws::KMS::Client to use. Defaults to +DEFAULT_REGION+
|
20
|
+
# @option options [String] :profile Use the specified profile from an AWS credentials file
|
21
|
+
#
|
22
|
+
def initialize(options = {})
|
23
|
+
@master_key = options[:master_key]
|
24
|
+
@region = options[:region]
|
25
|
+
@profile = options[:profile]
|
26
|
+
|
27
|
+
@kms = Aws::KMS::Client.new({
|
28
|
+
:region => region,
|
29
|
+
:profile => @profile,
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
# Lists all master key aliases available to the current client (Ignores built-aws keys that should not be used by user code).
|
34
|
+
# @return [Array]
|
35
|
+
def available_aliases
|
36
|
+
aliases = kms.list_aliases.aliases.delete_if { |a| a.alias_name.include? "alias/aws/"}
|
37
|
+
aliases.map{ |a| a.alias_name }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Key ARN of the currently selected master key
|
41
|
+
# @return [String] key ARN
|
42
|
+
def master_key_arn
|
43
|
+
master_key ? kms.describe_key({:key_id => master_key}).key_metadata.arn : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the key ID of the currently selected master key
|
47
|
+
# @return [String] key id
|
48
|
+
def master_key_id
|
49
|
+
master_key ? kms.describe_key({:key_id => master_key}).key_metadata.key_id : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# Sets the current master key using a key alias. Verifies that the provided key is available prior to setting.
|
53
|
+
# @param key_alias [String] Key alias to use, must be available with current credentials/role
|
54
|
+
# @return [String]
|
55
|
+
def use_key_alias=(key_alias)
|
56
|
+
if available_aliases.include? key_alias
|
57
|
+
@master_key = key_alias
|
58
|
+
else
|
59
|
+
raise "Requested key alias not available with current credentials!"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Current client region
|
64
|
+
# @return [String]
|
65
|
+
def region
|
66
|
+
@region ||= DEFAULT_REGION
|
67
|
+
end
|
68
|
+
|
69
|
+
# Short function to encode a blob to Base64
|
70
|
+
# @return [String] Base64 encoded string
|
71
|
+
def to_64(blob)
|
72
|
+
Base64.encode64(blob)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Short function to strict encode a blob to Base64
|
76
|
+
# @return [String] Base64 encoded string without linebreaks
|
77
|
+
def to_s64(blob)
|
78
|
+
Base64.strict_encode64(blob)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Short function to decode a blob from Base64
|
82
|
+
# @return [String] blob
|
83
|
+
def from_64(blob)
|
84
|
+
Base64.decode64(blob)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
desc 'Decrypt a text string or a file'
|
2
|
+
|
3
|
+
long_desc %{
|
4
|
+
Decrypt a text string or a file.
|
5
|
+
|
6
|
+
If an argument is not given, stdin will be used.
|
7
|
+
}
|
8
|
+
|
9
|
+
arg_name '[plaintext | path]'
|
10
|
+
command 'decrypt' do |c|
|
11
|
+
c.action do |global_options,options,args|
|
12
|
+
input = args.first || STDIN.read
|
13
|
+
KmsTools::CLI::Decrypt.new(global_options, options).decrypt(input)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
desc "Encrypt a text string or a file."
|
2
|
+
|
3
|
+
long_desc %{
|
4
|
+
Encrypt a text string or a file.
|
5
|
+
|
6
|
+
If an argument is not given, stdin will be encrypted with the specified key.
|
7
|
+
}
|
8
|
+
|
9
|
+
arg_name '[plaintext]'
|
10
|
+
command 'encrypt' do |c|
|
11
|
+
c.flag ['data-key'], :desc => "Encrypt data with the specified encrypted data key. If a path is provided, the key will be read from that file.\n\nNOTE: Data keys are required to encrypt more than 4KB of data."
|
12
|
+
c.action do |global_options,options,args|
|
13
|
+
input = args.first || STDIN.read
|
14
|
+
KmsTools::CLI::Encrypt.new(global_options, options).encrypt(input)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
desc 'List KMS key aliases available to current credentials'
|
2
|
+
command 'list-aliases' do |c|
|
3
|
+
c.action do |global_options,options,args|
|
4
|
+
num = 0
|
5
|
+
KmsTools::Base.new(
|
6
|
+
{ region: global_options[:region],
|
7
|
+
profile: global_options[:profile]
|
8
|
+
}
|
9
|
+
).available_aliases.each do |a|
|
10
|
+
puts "#{num += 1}. #{a}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module KmsTools
|
2
|
+
module CLI
|
3
|
+
# Class for handling decrypt operations from the CLI
|
4
|
+
class Decrypt
|
5
|
+
|
6
|
+
# Set up decryption handler
|
7
|
+
#
|
8
|
+
# @param [Hash] global_options
|
9
|
+
# @option global_options [String] :master_key Master key to use if provided by CLI options
|
10
|
+
# @option global_options [String] :profile AWS credential profile to us if provided by CLI options
|
11
|
+
# @option global_options [String] :region AWS region to use if provided by CLI options
|
12
|
+
def initialize(global_options, options)
|
13
|
+
@dec = KmsTools::Decrypter.new(global_options)
|
14
|
+
@output = options[:output]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Handle decrypt command
|
18
|
+
#
|
19
|
+
# @param [String] source Encrypted source to decrypt. Can be a string for direct decryption or a path to a KMS file to decrypt
|
20
|
+
# @return [String] Returns plaintext if Base64 encoded ciphertext is provided
|
21
|
+
# @return [String] Returns path to decrypted file if a source path is provided
|
22
|
+
def decrypt(source)
|
23
|
+
if File.file?(source)
|
24
|
+
save_path = decrypt_file(source)
|
25
|
+
Output.say("Decrypted file saved to: #{save_path}")
|
26
|
+
elsif source.is_a?(String)
|
27
|
+
decrypted_response = @dec.decrypt_string(source)
|
28
|
+
if STDIN.tty?
|
29
|
+
Output.say("Decrypted string:\n\n#{decrypted_response}\n\n")
|
30
|
+
else
|
31
|
+
print decrypted_response
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise "Unknown input to encrypt..."
|
35
|
+
end
|
36
|
+
source = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Decrypt a file from source path
|
40
|
+
#
|
41
|
+
# @param [String] source path to file to decrypt
|
42
|
+
# @return [String] path to decrypted file
|
43
|
+
def decrypt_file(source)
|
44
|
+
ef = KmsTools::EncryptedFile.new(path: source)
|
45
|
+
save_path = Helpers::get_save_path({
|
46
|
+
:prompt => "Save decrytped file to",
|
47
|
+
:suggested_path => File.absolute_path(source.sub File.extname(source), ef.original_extension)
|
48
|
+
})
|
49
|
+
ef.save_decrypted(save_path)
|
50
|
+
save_path
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|