secure_data_bag 1.1.4 → 2.0.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/.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
|
-
|