secure_data_bag 1.1.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/README.md +31 -48
- data/lib/chef/config.rb +14 -0
- data/lib/chef/knife/secure_bag_base.rb +40 -26
- data/lib/chef/knife/secure_bag_create.rb +4 -11
- data/lib/chef/knife/secure_bag_edit.rb +6 -20
- data/lib/chef/knife/secure_bag_from_file.rb +4 -10
- data/lib/chef/knife/secure_bag_show.rb +9 -10
- data/lib/secure_data_bag.rb +1 -2
- data/lib/secure_data_bag/secure_data_bag_item.rb +74 -64
- data/lib/secure_data_bag/version.rb +1 -1
- data/secure_data_bag.gemspec +3 -2
- data/spec/item_spec.rb +59 -0
- data/spec/spec_helper.rb +17 -0
- metadata +31 -13
- data/lib/secure_data_bag/decryptor.rb +0 -88
- data/lib/secure_data_bag/encryptor.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe94b528cef47838c539b57ce62634072750b5af
|
4
|
+
data.tar.gz: d6c2ca03370f5c26c6888ac411741972b0e631a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41ea852190213e90f432c4ade570d6b8280dc1778c2d16ec29316032b81cb47bd78c64ed9679f488ab0e292d28090bfed1c2f00b025f8a14ac725365521c7c9f
|
7
|
+
data.tar.gz: 026d10232c68d57bf8af33a9f6f75af0fda05576c3d28cb7ed6bde634ef751064d7ba674ecc9941044ff83e6673b566eefff8cc354828102104bb19c942c914d
|
data/.rspec
ADDED
data/README.md
CHANGED
@@ -22,6 +22,8 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
For the most part, this behaves exactly like a standard DataBagItem would. Encryption and Decryption of attributes ought to be completely transparent.
|
24
24
|
|
25
|
+
SecureDataBagItem is also able to read either standard DataBagItem objects or EncryptedDataBagItem since the encryption mechanism is on a per-key basis and is entirely compatible with EncryptedDataBagItem's format. One caveat, however, is that SecureDataBagItem can not be read by EncryptedDataBagItem.
|
26
|
+
|
25
27
|
SecureDataBagItem is also built on Mash rather than Hash so you'll find it more compatible with symbol keys.
|
26
28
|
|
27
29
|
```
|
@@ -30,17 +32,17 @@ SecureDataBagItem is also built on Mash rather than Hash so you'll find it more
|
|
30
32
|
|
31
33
|
> data = { id:"databag", "encoded":"my string", "unencoded":"other string" }
|
32
34
|
|
33
|
-
> item = SecureDataBagItem.from_hash(data, secret_key)
|
34
|
-
> item.raw_data # Unencoded hash
|
35
|
+
> item = SecureDataBagItem.from_hash(data, key: secret_key)
|
36
|
+
> item.raw_data # Unencoded hash
|
35
37
|
{
|
36
38
|
id: "databag",
|
37
|
-
encoded:
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
encoded_fields:[]
|
39
|
+
encoded: {
|
40
|
+
encrypted_data: "encoded",
|
41
|
+
cipher: aes-256-cbc,
|
42
|
+
iv: 13453453dkgfefg==
|
43
|
+
version: 1
|
43
44
|
}
|
45
|
+
unencoded: "other string",
|
44
46
|
}
|
45
47
|
|
46
48
|
> item.to_hash
|
@@ -49,54 +51,35 @@ SecureDataBagItem is also built on Mash rather than Hash so you'll find it more
|
|
49
51
|
chef_type: "data_bag_item",
|
50
52
|
data_bag: "",
|
51
53
|
encoded: "my string",
|
52
|
-
unencoded: "other string"
|
53
|
-
encryption:{
|
54
|
-
cipher:"aes-256-cbc",
|
55
|
-
iv:nil,
|
56
|
-
encoded_fields:[]
|
57
|
-
}
|
54
|
+
unencoded: "other string"
|
58
55
|
}
|
59
|
-
|
60
|
-
> item.encode_fields ["encoded"]
|
61
|
-
> item.to_hash # Encoded hash compatible with DataBagItem
|
62
|
-
{
|
63
|
-
id: "databag",
|
64
|
-
chef_type: "data_bag_item",
|
65
|
-
data_bag: "",
|
66
|
-
encoded: "[encoded]",
|
67
|
-
unencoded: "other string",
|
68
|
-
encryption:{
|
69
|
-
cipher:"aes-256-cbc",
|
70
|
-
iv:nil,
|
71
|
-
encoded_fields:["encoded"]
|
72
|
-
}
|
73
|
-
}
|
74
|
-
|
75
|
-
> item.raw_data
|
76
|
-
{
|
77
|
-
id: "databag",
|
78
|
-
chef_type: "data_bag_item",
|
79
|
-
data_bag: "",
|
80
|
-
encoded: "my string",
|
81
|
-
unencoded: "other string",
|
82
|
-
encryption:{
|
83
|
-
cipher:"aes-256-cbc",
|
84
|
-
iv:nil,
|
85
|
-
encoded_fields:[]
|
86
|
-
}
|
87
|
-
}
|
88
|
-
|
89
56
|
```
|
90
57
|
|
91
58
|
A few knife commands are also provided which allow you to create / edit / show / from file any DataBagItem or EncryptedDataBagItem and convert them to SecureDataBag::Item format.
|
92
59
|
|
93
60
|
```
|
94
|
-
knife secure bag
|
61
|
+
knife secure bag --help
|
62
|
+
** SECURE BAG COMMANDS **
|
63
|
+
knife secure bag create BAG [ITEM] (options)
|
64
|
+
knife secure bag edit BAG [ITEM] (options)
|
65
|
+
knife secure bag from file BAG FILE|FLDR [FILE|FLDR] (options)
|
66
|
+
knife secure bag show BAG [ITEM] (options)
|
67
|
+
```
|
68
|
+
|
69
|
+
Additionally it accepts the following command-line options :
|
95
70
|
|
96
|
-
|
71
|
+
```
|
72
|
+
--secret-file /path/to/secret.pem
|
73
|
+
--secret passcode
|
74
|
+
--encoded-fields field_one,field_two,field_three
|
75
|
+
```
|
97
76
|
|
98
|
-
|
77
|
+
Which may also be configured in knife.rb:
|
99
78
|
|
100
|
-
|
79
|
+
```
|
80
|
+
knife[:secure_data_bag] = {
|
81
|
+
fields: ["password","ssh_keys"],
|
82
|
+
secret_file: "/path/to/secret.pem"
|
83
|
+
}
|
101
84
|
```
|
102
85
|
|
data/lib/chef/config.rb
ADDED
@@ -19,51 +19,65 @@ class Chef
|
|
19
19
|
option :secret_file,
|
20
20
|
long: "--secret-file SECRET_FILE",
|
21
21
|
description: "A file containing a secret key to use to encrypt data bag item values",
|
22
|
-
proc: Proc.new { |sf|
|
22
|
+
proc: Proc.new { |sf|
|
23
|
+
Chef::Config[:encrypted_data_bag_secret] = sf
|
24
|
+
}
|
23
25
|
|
24
|
-
option :
|
25
|
-
long: "--
|
26
|
+
option :secure_data_bag_fields,
|
27
|
+
long: "--encoded-fields FIELD1,FIELD2,FIELD3",
|
26
28
|
description: "List of attribute keys for which to encode values",
|
27
|
-
|
29
|
+
proc: Proc.new { |o|
|
30
|
+
Chef::Config[:knife][:secure_data_bag][:fields] = o.split(",")
|
31
|
+
}
|
28
32
|
end
|
29
33
|
end
|
30
|
-
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
|
35
|
+
def encoded_fields(arg=nil)
|
36
|
+
config[:secure_data_bag_fields] = arg unless arg.nil?
|
37
|
+
config[:secure_data_bag_fields] ||
|
38
|
+
Chef::Config[:knife][:secure_data_bag][:fields]
|
35
39
|
end
|
36
40
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@raw_data.reject { |k,v| k == "id" }.
|
42
|
-
all? { |k,v| v.is_a?(Hash) and v.key? "encrypted_data" }
|
43
|
-
then super
|
44
|
-
else false
|
45
|
-
end
|
46
|
-
end
|
41
|
+
def secret_file
|
42
|
+
config[:secret] ||
|
43
|
+
Chef::Config[:knife][:secure_data_bag][:secret_file] ||
|
44
|
+
Chef::Config[:encrypted_data_bag_secret]
|
47
45
|
end
|
48
46
|
|
49
|
-
def
|
50
|
-
|
47
|
+
def use_encryption
|
48
|
+
true
|
51
49
|
end
|
52
50
|
|
53
|
-
def
|
54
|
-
[]
|
55
|
-
|
56
|
-
|
51
|
+
def read_secret
|
52
|
+
if config[:secret] then config[:secret]
|
53
|
+
else SecureDataBag::Item.load_secret(secret_file)
|
54
|
+
end
|
57
55
|
end
|
58
56
|
|
59
57
|
def require_secret
|
60
|
-
if not config[:secret] and not
|
58
|
+
if not config[:secret] and not secret_file
|
61
59
|
show_usage
|
62
60
|
ui.fatal("A secret or secret_file must be specified")
|
63
61
|
exit 1
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
65
|
+
def data_for_create(hash={})
|
66
|
+
hash[:id] = @data_bag_item_name
|
67
|
+
hash = data_for_edit(hash)
|
68
|
+
hash
|
69
|
+
end
|
70
|
+
|
71
|
+
def data_for_edit(hash)
|
72
|
+
hash[:_encoded_fields] = encoded_fields
|
73
|
+
hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def data_for_save(hash)
|
77
|
+
@encoded_fields = hash.delete(:_encoded_fields)
|
78
|
+
hash
|
79
|
+
end
|
80
|
+
|
67
81
|
end
|
68
82
|
end
|
69
83
|
end
|
@@ -21,20 +21,13 @@ class Chef
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def create_databag_item
|
24
|
-
create_object(
|
24
|
+
create_object(initial_data,
|
25
25
|
"data_bag_item[#{@data_bag_item_name}]") do |output|
|
26
26
|
|
27
|
-
@raw_data = output
|
27
|
+
@raw_data = data_for_save(output)
|
28
28
|
|
29
|
-
item =
|
30
|
-
|
31
|
-
encrypt_data_bag_item(output,read_secret)
|
32
|
-
else
|
33
|
-
output
|
34
|
-
end
|
35
|
-
|
36
|
-
item = SecureDataBag::Item.from_hash(item, read_secret)
|
37
|
-
item.encode_fields encoded_fields_for(item)
|
29
|
+
item = SecureDataBag::Item.from_hash(@raw_data, read_secret)
|
30
|
+
item.encoded_fields(encoded_fields)
|
38
31
|
item.data_bag(@data_bag_name)
|
39
32
|
|
40
33
|
rest.post_rest("data/#{@data_bag_name}", item.to_hash)
|
@@ -14,32 +14,18 @@ class Chef
|
|
14
14
|
item = Chef::DataBagItem.load(bag, item_name)
|
15
15
|
@raw_data = item.to_hash
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
item = SecureDataBag::Item.from_item(item, read_secret)
|
22
|
-
hash = item.to_hash(false)
|
23
|
-
#
|
24
|
-
# Ensure that we display encoded_fields
|
25
|
-
# - generally this would be blank given that all fields are decrypted
|
26
|
-
#
|
27
|
-
hash[:encryption][:encoded_fields] = encoded_fields_for(item)
|
17
|
+
item = SecureDataBag::Item.from_item(item, key:read_secret)
|
18
|
+
hash = item.to_hash(encoded: false)
|
19
|
+
hash = data_for_edit(hash)
|
28
20
|
hash
|
29
21
|
end
|
30
22
|
|
31
23
|
def edit_item(item)
|
32
24
|
output = super
|
25
|
+
output = data_for_save(output)
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
# until we have created the object
|
37
|
-
#
|
38
|
-
encode_fields = output["encryption"]["encoded_fields"]
|
39
|
-
output["encryption"]["encoded_fields"] = []
|
40
|
-
|
41
|
-
item = SecureDataBag::Item.from_hash(output, read_secret)
|
42
|
-
item.encode_fields encode_fields
|
27
|
+
item = SecureDataBag::Item.from_hash(output, key:read_secret)
|
28
|
+
item.encoded_fields encoded_fields
|
43
29
|
item.to_hash
|
44
30
|
end
|
45
31
|
end
|
@@ -12,7 +12,6 @@ class Chef
|
|
12
12
|
require 'chef/data_bag_item'
|
13
13
|
require 'chef/knife/core/object_loader'
|
14
14
|
require 'chef/json_compat'
|
15
|
-
require 'chef/encrypted_data_bag_item'
|
16
15
|
require 'secure_data_bag'
|
17
16
|
end
|
18
17
|
|
@@ -27,13 +26,7 @@ class Chef
|
|
27
26
|
def load_data_bag_hash(hash)
|
28
27
|
@raw_data = hash
|
29
28
|
|
30
|
-
|
31
|
-
item = Chef::EncryptedDataBagItem.
|
32
|
-
encrypt_data_bag_item(output, read_secret)
|
33
|
-
end
|
34
|
-
|
35
|
-
item = SecureDataBag::Item.from_hash(hash, read_secret)
|
36
|
-
item.encode_fields encoded_fields_for(item)
|
29
|
+
item = SecureDataBag::Item.from_hash(hash, secret:read_secret)
|
37
30
|
item.to_hash
|
38
31
|
end
|
39
32
|
|
@@ -43,9 +36,10 @@ class Chef
|
|
43
36
|
item_paths.each do |item_path|
|
44
37
|
item = loader.load_from("#{data_bags_path}", data_bag, item_path)
|
45
38
|
item = load_data_bag_hash(item)
|
46
|
-
dbag =
|
47
|
-
dbag.
|
39
|
+
dbag = SecureDataBag::Item.new(secret:read_secret)
|
40
|
+
dbag.encoded_fields encoded_fields
|
48
41
|
dbag.raw_data = item
|
42
|
+
dbag.data_bag(data_bag)
|
49
43
|
dbag.save
|
50
44
|
ui.info("Updated data_bag_item[#{dbag.data_bag}::#{dbag.id}]")
|
51
45
|
end
|
@@ -11,16 +11,15 @@ class Chef
|
|
11
11
|
category "secure bag"
|
12
12
|
|
13
13
|
def load_item(bag, item_name)
|
14
|
-
item =
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
data =
|
23
|
-
data[:encryption][:encoded_fields] = item.encode_fields
|
14
|
+
item = SecureDataBag::Item.load(
|
15
|
+
bag, item_name,
|
16
|
+
key: read_secret,
|
17
|
+
fields: encoded_fields
|
18
|
+
)
|
19
|
+
item.encoded_fields(encoded_fields)
|
20
|
+
|
21
|
+
data = item.to_hash(encoded:false)
|
22
|
+
data = data_for_edit(data)
|
24
23
|
data
|
25
24
|
end
|
26
25
|
|
data/lib/secure_data_bag.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
2
|
require 'open-uri'
|
3
3
|
require 'chef/data_bag_item'
|
4
|
+
require 'chef/encrypted_data_bag_item/encryptor'
|
5
|
+
require 'chef/encrypted_data_bag_item/decryptor'
|
4
6
|
|
5
7
|
module SecureDataBag
|
6
8
|
#
|
@@ -12,13 +14,21 @@ module SecureDataBag
|
|
12
14
|
#
|
13
15
|
|
14
16
|
class Item < Chef::DataBagItem
|
15
|
-
def initialize(key
|
17
|
+
def initialize(key:nil, fields:nil, secret:nil, data:nil)
|
16
18
|
super()
|
17
19
|
|
18
20
|
@secret = Chef::Config[:encrypted_data_bag_secret]
|
19
21
|
@key = key
|
20
|
-
|
21
|
-
|
22
|
+
|
23
|
+
unless data.nil?
|
24
|
+
self.raw_data = data
|
25
|
+
end
|
26
|
+
|
27
|
+
encoded_fields(
|
28
|
+
fields ||
|
29
|
+
Chef::Config[:knife][:secure_data_bag][:fields] ||
|
30
|
+
["password"]
|
31
|
+
)
|
22
32
|
end
|
23
33
|
|
24
34
|
#
|
@@ -39,7 +49,9 @@ module SecureDataBag
|
|
39
49
|
end
|
40
50
|
|
41
51
|
def self.load_secret(path=nil)
|
42
|
-
path ||=
|
52
|
+
path ||=
|
53
|
+
Chef::Config[:knife][:secure_data_bag][:secret_file] ||
|
54
|
+
Chef::Config[:encrypted_data_bag_secret]
|
43
55
|
|
44
56
|
unless path
|
45
57
|
raise ArgumentError, "No secret specified and no secret found."
|
@@ -68,20 +80,9 @@ module SecureDataBag
|
|
68
80
|
key
|
69
81
|
end
|
70
82
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
# - always ensure encryption settings have defaults
|
75
|
-
#
|
76
|
-
|
77
|
-
def encryption(arg=nil)
|
78
|
-
@raw_data[:encryption] ||= {}
|
79
|
-
@raw_data[:encryption] = arg unless arg.nil?
|
80
|
-
encryption = @raw_data[:encryption]
|
81
|
-
encryption[:iv] = nil if encryption[:iv].nil?
|
82
|
-
encryption[:cipher] = "aes-256-cbc" if encryption[:cipher].nil?
|
83
|
-
encryption[:encoded_fields] = [] if encryption[:encoded_fields].nil?
|
84
|
-
encryption
|
83
|
+
def self.load(data_bag, name, opts={})
|
84
|
+
data = super(data_bag, name)
|
85
|
+
new(data:data.to_hash, **opts)
|
85
86
|
end
|
86
87
|
|
87
88
|
#
|
@@ -94,89 +95,98 @@ module SecureDataBag
|
|
94
95
|
|
95
96
|
def raw_data=(data)
|
96
97
|
data = Mash.new(data)
|
97
|
-
super
|
98
|
-
encryption
|
98
|
+
super(data)
|
99
99
|
decode_data!
|
100
100
|
end
|
101
101
|
|
102
102
|
#
|
103
|
-
#
|
104
|
-
# - yeah, it's pretty naive
|
103
|
+
# Fields we wish to encode
|
105
104
|
#
|
106
105
|
|
107
|
-
def
|
108
|
-
|
106
|
+
def encoded_fields(arg=nil)
|
107
|
+
arg = arg.uniq if arg.is_a?(Array)
|
108
|
+
set_or_return(:encoded_fields, arg, kind_of: Array, default:[]).uniq
|
109
109
|
end
|
110
110
|
|
111
111
|
#
|
112
|
-
#
|
113
|
-
# - this differs from encryption[:encoded_fields] and will get merged
|
114
|
-
# into the latter upon an encode
|
112
|
+
# Raw Data decoder methods
|
115
113
|
#
|
116
114
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
115
|
+
def decode_data!
|
116
|
+
@raw_data = decoded_data
|
117
|
+
@raw_data
|
120
118
|
end
|
121
119
|
|
122
|
-
def
|
123
|
-
|
120
|
+
def decoded_data
|
121
|
+
if encoded_value?(@raw_data) then decode_value(@raw_data)
|
122
|
+
else decode_hash(@raw_data)
|
123
|
+
end
|
124
124
|
end
|
125
125
|
|
126
|
-
|
127
|
-
|
128
|
-
|
126
|
+
def decode_hash(hash)
|
127
|
+
hash.each do |k,v|
|
128
|
+
v = if encoded_value?(v) then decode_value(v)
|
129
|
+
elsif v.is_a?(Hash) then decode_hash(v)
|
130
|
+
else v
|
131
|
+
end
|
132
|
+
hash[k] = v
|
133
|
+
end
|
134
|
+
hash
|
135
|
+
end
|
129
136
|
|
130
|
-
def
|
131
|
-
|
132
|
-
# Ensure that we save previously encoded fields into our list of fields
|
133
|
-
# we wish to encode next time
|
134
|
-
#
|
135
|
-
encode_fields.concat(encryption[:encoded_fields]).uniq!
|
136
|
-
@raw_data = decoded_data if encoded?
|
137
|
-
@raw_data
|
137
|
+
def decode_value(value)
|
138
|
+
Chef::EncryptedDataBagItem::Decryptor.for(value, key).for_decrypted_item
|
138
139
|
end
|
139
140
|
|
140
|
-
def
|
141
|
-
|
142
|
-
data[:encryption][:encoded_fields] = []
|
143
|
-
data
|
141
|
+
def encoded_value?(value)
|
142
|
+
value.is_a?(Hash) and value.key?(:encrypted_data)
|
144
143
|
end
|
145
144
|
|
145
|
+
#
|
146
|
+
# Raw Data encoded methods
|
147
|
+
#
|
148
|
+
|
146
149
|
def encode_data!
|
147
150
|
@raw_data = encoded_data
|
151
|
+
@raw_data
|
148
152
|
end
|
149
153
|
|
150
154
|
def encoded_data
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
155
|
+
encode_hash(@raw_data.dup)
|
156
|
+
end
|
157
|
+
|
158
|
+
def encode_hash(hash)
|
159
|
+
hash.each do |k,v|
|
160
|
+
v = if encoded_fields.include?(k) then encode_value(v)
|
161
|
+
elsif v.is_a?(Hash) then encode_hash(v)
|
162
|
+
else v
|
163
|
+
end
|
164
|
+
hash[k] = v
|
165
|
+
end
|
166
|
+
hash
|
167
|
+
end
|
168
|
+
|
169
|
+
def encode_value(value)
|
170
|
+
Chef::EncryptedDataBagItem::Encryptor.new(value, key).for_encrypted_item
|
160
171
|
end
|
161
172
|
|
162
173
|
#
|
163
174
|
# Transitions
|
164
175
|
#
|
165
176
|
|
166
|
-
def self.from_hash(h,
|
167
|
-
item = new(
|
168
|
-
item.raw_data = h
|
177
|
+
def self.from_hash(h, opts={})
|
178
|
+
item = new(data:h, **opts)
|
169
179
|
item
|
170
180
|
end
|
171
181
|
|
172
|
-
def self.from_item(h,
|
173
|
-
item = self.from_hash(h.to_hash,
|
182
|
+
def self.from_item(h, opts={})
|
183
|
+
item = self.from_hash(h.to_hash, **opts)
|
174
184
|
item.data_bag h.data_bag
|
175
185
|
item
|
176
186
|
end
|
177
187
|
|
178
|
-
def to_hash(encoded
|
179
|
-
result = encoded ? encoded_data :
|
188
|
+
def to_hash(encoded: true)
|
189
|
+
result = encoded ? encoded_data : @raw_data
|
180
190
|
result["chef_type"] = "data_bag_item"
|
181
191
|
result["data_bag"] = self.data_bag
|
182
192
|
result
|
@@ -187,7 +197,7 @@ module SecureDataBag
|
|
187
197
|
name: self.object_name,
|
188
198
|
json_class: "Chef::DataBagItem",
|
189
199
|
chef_type: "data_bag_item",
|
190
|
-
data_bag:
|
200
|
+
data_bag: data_bag,
|
191
201
|
raw_data: encoded_data
|
192
202
|
}
|
193
203
|
result.to_json(*a)
|
data/secure_data_bag.gemspec
CHANGED
@@ -18,9 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency
|
22
|
-
spec.add_dependency 'yajl-ruby'
|
21
|
+
spec.add_dependency "chef"
|
23
22
|
|
24
23
|
spec.add_development_dependency "bundler", "~> 1.6"
|
25
24
|
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "chef"
|
26
27
|
end
|
data/spec/item_spec.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'chef/data_bag_item'
|
4
|
+
require 'chef/encrypted_data_bag_item'
|
5
|
+
|
6
|
+
describe SecureDataBag::Item do
|
7
|
+
let(:key) { "password" }
|
8
|
+
let(:secret) { "data/secret.pem" }
|
9
|
+
let(:fields) { ["encoded"] }
|
10
|
+
|
11
|
+
let(:simple_data) { { "id"=>"test", decoded:"decoded", encoded:"encoded" } }
|
12
|
+
let(:nested_data) { simple_data.merge({ nested:simple_data }) }
|
13
|
+
|
14
|
+
let(:item) { SecureDataBag::Item.new(key:key, fields:fields) }
|
15
|
+
|
16
|
+
let(:dbag_item) do
|
17
|
+
dbag_item = Chef::DataBagItem.new
|
18
|
+
dbag_item.raw_data = simple_data
|
19
|
+
dbag_item
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:simple_item) do
|
23
|
+
simple_item = item
|
24
|
+
simple_item.raw_data = simple_data
|
25
|
+
simple_item
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:nested_item) do
|
29
|
+
nested_item = item
|
30
|
+
nested_item.raw_data = nested_data
|
31
|
+
nested_item
|
32
|
+
end
|
33
|
+
|
34
|
+
it "encodes simple data" do
|
35
|
+
dbag = simple_item
|
36
|
+
data = dbag.encoded_data
|
37
|
+
expect(data[:decoded]).to eq("decoded")
|
38
|
+
expect(data[:encoded]).not_to eq("encoded")
|
39
|
+
expect(data[:encoded][:encrypted_data]).not_to eq(nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "encodes nested data" do
|
43
|
+
dbag = nested_item
|
44
|
+
data = dbag.encoded_data
|
45
|
+
expect(data[:nested][:decoded]).to eq("decoded")
|
46
|
+
expect(data[:nested][:encoded]).not_to eq("encoded")
|
47
|
+
expect(data[:nested][:encoded][:encrypted_data]).not_to eq(nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "decodes encrypted_data_bag_item" do
|
51
|
+
edbag = Chef::EncryptedDataBagItem.encrypt_data_bag_item(dbag_item, key)
|
52
|
+
dbag = item
|
53
|
+
dbag.raw_data = edbag
|
54
|
+
data = dbag.raw_data
|
55
|
+
expect(data[:decoded]).to eq("decoded")
|
56
|
+
expect(data[:encoded]).to eq("encoded")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'secure_data_bag'
|
6
|
+
require 'chef/config'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.expect_with :rspec do |expectations|
|
10
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
11
|
+
end
|
12
|
+
|
13
|
+
config.mock_with :rspec do |mocks|
|
14
|
+
mocks.verify_partial_doubles = true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secure_data_bag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Serafini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef
|
@@ -25,13 +25,27 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - '>='
|
32
46
|
- !ruby/object:Gem::Version
|
33
47
|
version: '0'
|
34
|
-
type: :
|
48
|
+
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
@@ -39,21 +53,21 @@ dependencies:
|
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
|
-
- -
|
59
|
+
- - '>='
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '0'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
|
-
- -
|
66
|
+
- - '>='
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: chef
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - '>='
|
@@ -75,10 +89,12 @@ extensions: []
|
|
75
89
|
extra_rdoc_files: []
|
76
90
|
files:
|
77
91
|
- .gitignore
|
92
|
+
- .rspec
|
78
93
|
- Gemfile
|
79
94
|
- LICENSE.txt
|
80
95
|
- README.md
|
81
96
|
- Rakefile
|
97
|
+
- lib/chef/config.rb
|
82
98
|
- lib/chef/dsl/data_query.rb
|
83
99
|
- lib/chef/knife/secure_bag_base.rb
|
84
100
|
- lib/chef/knife/secure_bag_create.rb
|
@@ -86,11 +102,11 @@ files:
|
|
86
102
|
- lib/chef/knife/secure_bag_from_file.rb
|
87
103
|
- lib/chef/knife/secure_bag_show.rb
|
88
104
|
- lib/secure_data_bag.rb
|
89
|
-
- lib/secure_data_bag/decryptor.rb
|
90
|
-
- lib/secure_data_bag/encryptor.rb
|
91
105
|
- lib/secure_data_bag/secure_data_bag_item.rb
|
92
106
|
- lib/secure_data_bag/version.rb
|
93
107
|
- secure_data_bag.gemspec
|
108
|
+
- spec/item_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
94
110
|
homepage: https://github.com/JonathanSerafini/chef-secure_data_bag
|
95
111
|
licenses:
|
96
112
|
- MIT
|
@@ -115,4 +131,6 @@ rubygems_version: 2.1.11
|
|
115
131
|
signing_key:
|
116
132
|
specification_version: 4
|
117
133
|
summary: Per-field data bag item encryption
|
118
|
-
test_files:
|
134
|
+
test_files:
|
135
|
+
- spec/item_spec.rb
|
136
|
+
- spec/spec_helper.rb
|
@@ -1,88 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'yaml'
|
3
|
-
require 'yajl'
|
4
|
-
require 'openssl'
|
5
|
-
require 'base64'
|
6
|
-
require 'digest/sha2'
|
7
|
-
|
8
|
-
module SecureDataBag
|
9
|
-
class Item
|
10
|
-
class Decryptor
|
11
|
-
class DecryptionFailure < StandardError
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize(encrypted_hash, encryption, key)
|
15
|
-
@encryption = encryption
|
16
|
-
@encrypted_hash = encrypted_hash
|
17
|
-
@key = key
|
18
|
-
@iv = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def for_decrypted_item
|
22
|
-
decrypted_hash
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_reader :encrypted_hash
|
26
|
-
attr_reader :encryption
|
27
|
-
attr_reader :key
|
28
|
-
|
29
|
-
def iv
|
30
|
-
@iv ||= begin
|
31
|
-
iv_string = encryption[:iv]
|
32
|
-
Base64.decode64(iv_string)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def decrypted_hash
|
37
|
-
@decrypted_hash ||= begin
|
38
|
-
decrypt_hash(encrypted_hash.dup)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def decrypt_hash(hash)
|
43
|
-
hash.each do |k,v|
|
44
|
-
if encryption[:encoded_fields].include?(k)
|
45
|
-
begin
|
46
|
-
v = decrypt_value(v)
|
47
|
-
rescue Yajl::ParseError
|
48
|
-
raise DecryptionFailure,
|
49
|
-
"Error decrypting data bag value for #{k}."
|
50
|
-
rescue OpenSSL::Cipher::CipherError => e
|
51
|
-
raise DecryptionFailure,
|
52
|
-
"Error decrypting data bag value for #{k}: #{e.message}"
|
53
|
-
end
|
54
|
-
elsif v.is_a? Hash
|
55
|
-
v = decrypt_hash(v)
|
56
|
-
end
|
57
|
-
hash[k] = v
|
58
|
-
end
|
59
|
-
hash
|
60
|
-
end
|
61
|
-
|
62
|
-
def decrypt_value(value)
|
63
|
-
if value.is_a? String and not value.empty?
|
64
|
-
value = Base64.decode64(value)
|
65
|
-
value = openssl_decryptor.update(value)
|
66
|
-
value << openssl_decryptor.final
|
67
|
-
|
68
|
-
if value.include? "json_wrapper"
|
69
|
-
value = Yajl::Parser.parse(value)["json_wrapper"]
|
70
|
-
end
|
71
|
-
@openssl_decryptor = nil
|
72
|
-
end
|
73
|
-
value
|
74
|
-
end
|
75
|
-
|
76
|
-
def openssl_decryptor
|
77
|
-
@openssl_decryptor ||= begin
|
78
|
-
d = OpenSSL::Cipher::Cipher.new(encryption[:cipher])
|
79
|
-
d.decrypt
|
80
|
-
d.key = Digest::SHA256.digest(key)
|
81
|
-
d.iv = iv
|
82
|
-
d
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
@@ -1,88 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'yaml'
|
3
|
-
require 'yajl'
|
4
|
-
require 'openssl'
|
5
|
-
require 'base64'
|
6
|
-
require 'digest/sha2'
|
7
|
-
|
8
|
-
module SecureDataBag
|
9
|
-
class Item
|
10
|
-
class Encryptor
|
11
|
-
def initialize(unencrypted_hash, encryption, key)
|
12
|
-
@encryption = encryption
|
13
|
-
@unencrypted_hash = unencrypted_hash
|
14
|
-
@encoded_fields = []
|
15
|
-
@key = key
|
16
|
-
end
|
17
|
-
|
18
|
-
def for_encrypted_item
|
19
|
-
data = encrypted_hash
|
20
|
-
encryption_hash = encryption.dup
|
21
|
-
encryption_hash[:iv] = Base64.encode64(encryption_hash[:iv] || "")
|
22
|
-
encryption_hash[:encoded_fields] = encoded_fields.uniq
|
23
|
-
data.merge({encryption:encryption_hash})
|
24
|
-
end
|
25
|
-
|
26
|
-
attr_reader :unencrypted_hash
|
27
|
-
attr_reader :encoded_fields
|
28
|
-
attr_reader :encryption
|
29
|
-
attr_reader :key
|
30
|
-
|
31
|
-
def encrypted_hash
|
32
|
-
@encrypted_data ||= begin
|
33
|
-
encrypt_hash(unencrypted_hash.dup)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def encrypt_hash(hash)
|
38
|
-
hash.each do |k,v|
|
39
|
-
if encryption[:encoded_fields].include?(k)
|
40
|
-
v = encrypt_value(v)
|
41
|
-
encoded_fields << k
|
42
|
-
elsif v.is_a? Hash
|
43
|
-
v = encrypt_hash(v)
|
44
|
-
end
|
45
|
-
hash[k] = v
|
46
|
-
end
|
47
|
-
hash
|
48
|
-
end
|
49
|
-
|
50
|
-
def encrypt_value(value)
|
51
|
-
value = normalize_value(value)
|
52
|
-
|
53
|
-
if not value.nil? and not value.empty?
|
54
|
-
value = openssl_encryptor.update(value)
|
55
|
-
value << openssl_encryptor.final
|
56
|
-
@openssl_encryptor = nil
|
57
|
-
value = Base64.encode64(value)
|
58
|
-
end
|
59
|
-
|
60
|
-
value
|
61
|
-
end
|
62
|
-
|
63
|
-
def normalize_value(value)
|
64
|
-
if [Hash,Array].any? {|c| value.is_a? c}
|
65
|
-
serialize_value(value)
|
66
|
-
else
|
67
|
-
value.to_s
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def serialize_value(value)
|
72
|
-
Yajl::Encoder.encode(:json_wrapper => value)
|
73
|
-
end
|
74
|
-
|
75
|
-
def openssl_encryptor
|
76
|
-
@openssl_encryptor ||= begin
|
77
|
-
encryptor = OpenSSL::Cipher::Cipher.new(encryption[:cipher])
|
78
|
-
encryptor.encrypt
|
79
|
-
encryption[:iv] ||= encryptor.random_iv
|
80
|
-
encryptor.iv = encryption[:iv]
|
81
|
-
encryptor.key = Digest::SHA256.digest(key)
|
82
|
-
encryptor
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|