porky_lib 0.1.5 → 0.2.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 +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
|