porky_lib 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +27 -22
- data/README.md +16 -0
- data/lib/porky_lib.rb +2 -0
- data/lib/porky_lib/config.rb +3 -1
- data/lib/porky_lib/file_service.rb +119 -0
- data/lib/porky_lib/symmetric.rb +2 -2
- data/lib/porky_lib/version.rb +1 -1
- data/porky_lib.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46e66e6a979326e1996e6e72466d805b81db819f
|
4
|
+
data.tar.gz: 357cec74b604085e0452a2b6ae9ecd94f07b288e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dd9de7ff39b6bba06957262223e244f221c0241ceb816cce9718a0a5d20cb4f5771a69564f563fb60ce5fa4a5cd06162f02f76803d80e8776e1792adad350a9
|
7
|
+
data.tar.gz: 3212f9cbd316bc2e4ff635ef357ebe77cca6c6a91989d2d1fc5dd196cb40b2c968a5499a1756084a697431f72ae203da1f8a06057e44e0fffd0ee9862bb7db74
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
porky_lib (0.
|
4
|
+
porky_lib (0.2.0)
|
5
5
|
aws-sdk-kms
|
6
|
+
aws-sdk-s3
|
6
7
|
msgpack
|
7
8
|
rbnacl-libsodium
|
8
9
|
|
@@ -11,14 +12,18 @@ GEM
|
|
11
12
|
specs:
|
12
13
|
ast (2.4.0)
|
13
14
|
aws-eventstream (1.0.1)
|
14
|
-
aws-partitions (1.
|
15
|
-
aws-sdk-core (3.
|
15
|
+
aws-partitions (1.104.0)
|
16
|
+
aws-sdk-core (3.27.0)
|
16
17
|
aws-eventstream (~> 1.0)
|
17
18
|
aws-partitions (~> 1.0)
|
18
19
|
aws-sigv4 (~> 1.0)
|
19
20
|
jmespath (~> 1.0)
|
20
|
-
aws-sdk-kms (1.
|
21
|
-
aws-sdk-core (~> 3)
|
21
|
+
aws-sdk-kms (1.9.0)
|
22
|
+
aws-sdk-core (~> 3, >= 3.26.0)
|
23
|
+
aws-sigv4 (~> 1.0)
|
24
|
+
aws-sdk-s3 (1.19.0)
|
25
|
+
aws-sdk-core (~> 3, >= 3.26.0)
|
26
|
+
aws-sdk-kms (~> 1)
|
22
27
|
aws-sigv4 (~> 1.0)
|
23
28
|
aws-sigv4 (1.0.3)
|
24
29
|
bundler-audit (0.6.0)
|
@@ -37,7 +42,7 @@ GEM
|
|
37
42
|
json (2.1.0)
|
38
43
|
msgpack (1.2.4)
|
39
44
|
parallel (1.12.1)
|
40
|
-
parser (2.5.1.
|
45
|
+
parser (2.5.1.2)
|
41
46
|
ast (~> 2.4.0)
|
42
47
|
powerpack (0.1.2)
|
43
48
|
rainbow (3.0.0)
|
@@ -46,35 +51,35 @@ GEM
|
|
46
51
|
ffi
|
47
52
|
rbnacl-libsodium (1.0.16)
|
48
53
|
rbnacl (>= 3.0.1)
|
49
|
-
rspec (3.
|
50
|
-
rspec-core (~> 3.
|
51
|
-
rspec-expectations (~> 3.
|
52
|
-
rspec-mocks (~> 3.
|
54
|
+
rspec (3.8.0)
|
55
|
+
rspec-core (~> 3.8.0)
|
56
|
+
rspec-expectations (~> 3.8.0)
|
57
|
+
rspec-mocks (~> 3.8.0)
|
53
58
|
rspec-collection_matchers (1.1.3)
|
54
59
|
rspec-expectations (>= 2.99.0.beta1)
|
55
|
-
rspec-core (3.
|
56
|
-
rspec-support (~> 3.
|
57
|
-
rspec-expectations (3.
|
60
|
+
rspec-core (3.8.0)
|
61
|
+
rspec-support (~> 3.8.0)
|
62
|
+
rspec-expectations (3.8.1)
|
58
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
59
|
-
rspec-support (~> 3.
|
60
|
-
rspec-mocks (3.
|
64
|
+
rspec-support (~> 3.8.0)
|
65
|
+
rspec-mocks (3.8.0)
|
61
66
|
diff-lcs (>= 1.2.0, < 2.0)
|
62
|
-
rspec-support (~> 3.
|
63
|
-
rspec-support (3.
|
67
|
+
rspec-support (~> 3.8.0)
|
68
|
+
rspec-support (3.8.0)
|
64
69
|
rspec_junit_formatter (0.4.1)
|
65
70
|
rspec-core (>= 2, < 4, != 2.12.0)
|
66
|
-
rubocop (0.
|
71
|
+
rubocop (0.59.1)
|
67
72
|
jaro_winkler (~> 1.5.1)
|
68
73
|
parallel (~> 1.10)
|
69
|
-
parser (>= 2.5)
|
74
|
+
parser (>= 2.5, != 2.5.1.1)
|
70
75
|
powerpack (~> 0.1)
|
71
76
|
rainbow (>= 2.2.2, < 4.0)
|
72
77
|
ruby-progressbar (~> 1.7)
|
73
78
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
74
|
-
rubocop-rspec (1.
|
75
|
-
rubocop (>= 0.
|
79
|
+
rubocop-rspec (1.29.1)
|
80
|
+
rubocop (>= 0.58.0)
|
76
81
|
rubocop_runner (2.1.0)
|
77
|
-
ruby-progressbar (1.
|
82
|
+
ruby-progressbar (1.10.0)
|
78
83
|
simplecov (0.16.1)
|
79
84
|
docile (~> 1.1)
|
80
85
|
json (>= 1.8, < 3)
|
data/README.md
CHANGED
@@ -116,6 +116,22 @@ To verify whether an alias exists or not:
|
|
116
116
|
alias_exists = PorkyLib::Symmetric.instance.cmk_alias_exists?(key_alias)
|
117
117
|
```
|
118
118
|
|
119
|
+
### To Read From AWS S3
|
120
|
+
```ruby
|
121
|
+
# Where bucket_name is the name of the S3 bucket to read from
|
122
|
+
# file_key is file identifier of the file/data that was written to S3.
|
123
|
+
file_data = PorkyLib::FileService.read(bucket_name, file_key)
|
124
|
+
```
|
125
|
+
|
126
|
+
### To Write To AWS S3
|
127
|
+
```ruby
|
128
|
+
# Where file is the data to encrypt and upload to S3 (can be raw data or path to a file on disk)
|
129
|
+
# bucket_name is the name of the S3 bucket to write to
|
130
|
+
# key_id is the ID of the CMK to use to generate a data encryption key to encrypt the file data
|
131
|
+
# options is an optional parameter for specifying optional metadata about the file
|
132
|
+
file_key = PorkyLib::FileService.write(file, bucket_name, key_id, options)
|
133
|
+
```
|
134
|
+
|
119
135
|
## Development
|
120
136
|
|
121
137
|
Development on this project should occur on separate feature branches and pull requests should be submitted. When submitting a
|
data/lib/porky_lib.rb
CHANGED
data/lib/porky_lib/config.rb
CHANGED
@@ -5,12 +5,14 @@ class PorkyLib::Config
|
|
5
5
|
@aws_key_id = ''
|
6
6
|
@aws_key_secret = ''
|
7
7
|
@aws_client_mock = false
|
8
|
+
@max_file_size = 0
|
8
9
|
|
9
10
|
@config = {
|
10
11
|
aws_region: @aws_region,
|
11
12
|
aws_key_id: @aws_key_id,
|
12
13
|
aws_key_secret: @aws_key_secret,
|
13
|
-
aws_client_mock: @aws_client_mock
|
14
|
+
aws_client_mock: @aws_client_mock,
|
15
|
+
max_file_size: @max_file_size
|
14
16
|
}
|
15
17
|
|
16
18
|
@allowed_config_keys = @config.keys
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-s3'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
class PorkyLib::FileService
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
class FileServiceError < StandardError; end
|
10
|
+
class FileSizeTooLargeError < StandardError; end
|
11
|
+
|
12
|
+
def read(bucket_name, file_key, options = {})
|
13
|
+
tempfile = Tempfile.new
|
14
|
+
|
15
|
+
begin
|
16
|
+
object = s3.bucket(bucket_name).object(file_key)
|
17
|
+
raise FileSizeTooLargeError, "File size is larger than maximum allowed size of #{max_file_size}" if object.content_length > max_size
|
18
|
+
|
19
|
+
object.download_file(tempfile.path, options)
|
20
|
+
rescue Aws::Errors::ServiceError => e
|
21
|
+
raise FileServiceError, "Attempt to download a file from S3 failed.\n#{e.message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
decrypt_file_contents(tempfile)
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(file, bucket_name, key_id, options = {})
|
28
|
+
raise FileServiceError, 'Invalid input. One or more input values is nil' if input_invalid?(file, bucket_name, key_id)
|
29
|
+
raise FileSizeTooLargeError, "File size is larger than maximum allowed size of #{max_file_size}" if file_size_invalid?(file)
|
30
|
+
|
31
|
+
data = file_data(file)
|
32
|
+
file_key = SecureRandom.uuid
|
33
|
+
tempfile = encrypt_file_contents(data, key_id, file_key)
|
34
|
+
|
35
|
+
begin
|
36
|
+
perform_upload(bucket_name, file_key, tempfile, options)
|
37
|
+
rescue Aws::Errors::ServiceError => e
|
38
|
+
raise FileServiceError, "Attempt to upload a file to S3 failed.\n#{e.message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Remove tempfile from disk
|
42
|
+
tempfile.unlink
|
43
|
+
file_key
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def input_invalid?(file, bucket_name, key_id)
|
49
|
+
file.nil? || bucket_name.nil? || key_id.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
def file_size_invalid?(file)
|
53
|
+
file.bytesize > max_size || (File.file?(file) && File.size(file) > max_size)
|
54
|
+
end
|
55
|
+
|
56
|
+
def file_data(file)
|
57
|
+
File.file?(file) ? File.read(file) : file
|
58
|
+
end
|
59
|
+
|
60
|
+
def max_size
|
61
|
+
PorkyLib::Config.config[:max_file_size]
|
62
|
+
end
|
63
|
+
|
64
|
+
def max_file_size
|
65
|
+
{
|
66
|
+
B: 1024,
|
67
|
+
KB: 1024 * 1024,
|
68
|
+
MB: 1024 * 1024 * 1024,
|
69
|
+
GB: 1024 * 1024 * 1024 * 1024
|
70
|
+
}.each_pair { |symbol, bytes| return "#{(max_size.to_f / (bytes / 1024)).round(2)}#{symbol}" if max_size < bytes }
|
71
|
+
end
|
72
|
+
|
73
|
+
def perform_upload(bucket_name, file_key, tempfile, options)
|
74
|
+
obj = s3.bucket(bucket_name).object(file_key)
|
75
|
+
if options.key?(:metadata)
|
76
|
+
obj.upload_file(tempfile.path, options[:metadata])
|
77
|
+
else
|
78
|
+
obj.upload_file(tempfile.path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def decrypt_file_contents(tempfile)
|
83
|
+
file_contents = tempfile.read
|
84
|
+
|
85
|
+
# Remove tempfile from disk
|
86
|
+
tempfile.unlink
|
87
|
+
|
88
|
+
ciphertext_data = JSON.parse(file_contents, symbolize_names: true)
|
89
|
+
ciphertext_key = Base64.urlsafe_decode64(ciphertext_data[:key])
|
90
|
+
ciphertext = Base64.urlsafe_decode64(ciphertext_data[:data])
|
91
|
+
nonce = Base64.urlsafe_decode64(ciphertext_data[:nonce])
|
92
|
+
|
93
|
+
PorkyLib::Symmetric.instance.decrypt(ciphertext_key, ciphertext, nonce)
|
94
|
+
end
|
95
|
+
|
96
|
+
def encrypt_file_contents(file, key_id, file_key)
|
97
|
+
ciphertext_key, ciphertext, nonce = PorkyLib::Symmetric.instance.encrypt(file, key_id)
|
98
|
+
|
99
|
+
file_contents = {
|
100
|
+
key: Base64.urlsafe_encode64(ciphertext_key),
|
101
|
+
data: Base64.urlsafe_encode64(ciphertext),
|
102
|
+
nonce: Base64.urlsafe_encode64(nonce)
|
103
|
+
}.to_json
|
104
|
+
|
105
|
+
write_tempfile(file_contents, file_key)
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_tempfile(file_contents, file_key)
|
109
|
+
tempfile = Tempfile.new(file_key)
|
110
|
+
tempfile << file_contents
|
111
|
+
tempfile.close
|
112
|
+
|
113
|
+
tempfile
|
114
|
+
end
|
115
|
+
|
116
|
+
def s3
|
117
|
+
@s3 ||= Aws::S3::Resource.new
|
118
|
+
end
|
119
|
+
end
|
data/lib/porky_lib/symmetric.rb
CHANGED
@@ -56,6 +56,7 @@ class PorkyLib::Symmetric
|
|
56
56
|
|
57
57
|
def decrypt_data_encryption_key(ciphertext_key, encryption_context = nil)
|
58
58
|
return client.decrypt(ciphertext_blob: ciphertext_key, encryption_context: encryption_context).to_h[:plaintext] if encryption_context
|
59
|
+
|
59
60
|
client.decrypt(ciphertext_blob: ciphertext_key).to_h[:plaintext]
|
60
61
|
end
|
61
62
|
|
@@ -94,8 +95,7 @@ class PorkyLib::Symmetric
|
|
94
95
|
# Securely delete the plaintext value from memory
|
95
96
|
plaintext_key.replace(secure_delete_plaintext_key(plaintext_key.bytesize))
|
96
97
|
|
97
|
-
|
98
|
-
result
|
98
|
+
secret_box.decrypt(nonce, ciphertext)
|
99
99
|
end
|
100
100
|
|
101
101
|
def secure_delete_plaintext_key(length)
|
data/lib/porky_lib/version.rb
CHANGED
data/porky_lib.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: porky_lib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Fletcher
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -220,6 +220,20 @@ dependencies:
|
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: aws-sdk-s3
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :runtime
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
223
237
|
- !ruby/object:Gem::Dependency
|
224
238
|
name: msgpack
|
225
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -269,6 +283,7 @@ files:
|
|
269
283
|
- lib/porky_lib.rb
|
270
284
|
- lib/porky_lib/aws/kms/client.rb
|
271
285
|
- lib/porky_lib/config.rb
|
286
|
+
- lib/porky_lib/file_service.rb
|
272
287
|
- lib/porky_lib/symmetric.rb
|
273
288
|
- lib/porky_lib/version.rb
|
274
289
|
- porky_lib.gemspec
|