secure_data_bag 1.0.5 → 1.1.1
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/README.md +71 -9
- data/lib/chef/dsl/data_query.rb +23 -0
- data/lib/chef/knife/secure_bag_base.rb +64 -0
- data/lib/chef/knife/secure_bag_create.rb +72 -0
- data/lib/chef/knife/secure_bag_edit.rb +48 -0
- data/lib/chef/knife/secure_bag_from_file.rb +56 -0
- data/lib/chef/knife/secure_bag_show.rb +41 -0
- data/lib/secure_data_bag.rb +3 -27
- data/lib/secure_data_bag/decryptor.rb +14 -13
- data/lib/secure_data_bag/encryptor.rb +6 -7
- data/lib/secure_data_bag/secure_data_bag_item.rb +103 -73
- data/lib/secure_data_bag/version.rb +1 -1
- data/secure_data_bag.gemspec +8 -5
- metadata +52 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00d1848993d374b1b9cc72055cb378eda434cabe
|
4
|
+
data.tar.gz: 1e13eb1b536318100059af425078fda53427c2b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1945a42718d9f1c9c3a72ca4e19a729b0109a4471e2b6881287a4b24108ddf435e8f6778d379e106be6f1e5754f3a34cdf57d9108ab5bfa3f692bc09d7c1e02
|
7
|
+
data.tar.gz: b5208299a8b4fc1ea9fe10b7bb4b008ee4c9b2d2e68ff097155a7bceb42d062f6c34bcef3aa81142ec5946ce141c61d9308fb0252da64f25d308ce06e33fd4b0
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Provides a mechanism to partially encrypt data bag items on a per-key basis which gives us the opportunity to still search for every other field.
|
4
4
|
|
5
|
+
When specifying keys to encrypt, the library will recursively walk through the data bag content searching for encryption candidates.
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
Add this line to your application's Gemfile:
|
@@ -23,18 +25,78 @@ For the most part, this behaves exactly like a standard DataBagItem would. Encry
|
|
23
25
|
SecureDataBagItem is also built on Mash rather than Hash so you'll find it more compatible with symbol keys.
|
24
26
|
|
25
27
|
```
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
> secret_key = SecureDataBagItem.load_key("/path/to/secret")
|
29
|
+
> secret_key = nil # Load default secret
|
30
|
+
|
31
|
+
> data = { id:"databag", "encoded":"my string", "unencoded":"other string" }
|
32
|
+
|
33
|
+
> item = SecureDataBagItem.from_hash(data, secret_key)
|
34
|
+
> item.raw_data # Unencoded hash with data[:encryption] added
|
35
|
+
{
|
36
|
+
id: "databag",
|
37
|
+
encoded: "my string",
|
38
|
+
unencoded: "other string",
|
39
|
+
encryption:{
|
40
|
+
cipher:"aes-256-cbc",
|
41
|
+
iv:nil,
|
42
|
+
encoded_fields:[]
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
> item.to_hash
|
47
|
+
{
|
48
|
+
id: "databag",
|
49
|
+
chef_type: "data_bag_item",
|
50
|
+
data_bag: "",
|
51
|
+
encoded: "my string",
|
52
|
+
unencoded: "other string",
|
53
|
+
encryption:{
|
54
|
+
cipher:"aes-256-cbc",
|
55
|
+
iv:nil,
|
56
|
+
encoded_fields:[]
|
57
|
+
}
|
58
|
+
}
|
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
|
+
|
31
89
|
```
|
32
90
|
|
33
|
-
|
91
|
+
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.
|
34
92
|
|
35
93
|
```
|
36
|
-
item
|
37
|
-
|
38
|
-
item
|
94
|
+
knife secure bag create data_bag item
|
95
|
+
|
96
|
+
knife secure bag edit data_bag item
|
97
|
+
|
98
|
+
knife secure bag show data_bag item
|
99
|
+
|
100
|
+
knife secure bag from file data_bag /path/to/item.json
|
39
101
|
```
|
40
102
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
class Chef
|
3
|
+
module DSL
|
4
|
+
module DataQuery
|
5
|
+
def secure_data_bag_item(bag, item)
|
6
|
+
DataBag.validate_name!(bag.to_s)
|
7
|
+
SecureDataBag::Item.validate_id!(item)
|
8
|
+
SecureDataBag::Item.load(bag, item)
|
9
|
+
rescue Exception
|
10
|
+
Log.error("Failed to load secure data bag item: #{bag.inspect} #{item.inspect}")
|
11
|
+
raise
|
12
|
+
end
|
13
|
+
|
14
|
+
def secure_data_bag_item!(item, fields=[])
|
15
|
+
secure = SecureDataBag::Item.from_item item
|
16
|
+
secure.encoded_fields.concat(Array(fields))
|
17
|
+
secure
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
require 'chef/knife'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
class Knife
|
6
|
+
module SecureBagBase
|
7
|
+
def self.included(includer)
|
8
|
+
includer.class_eval do
|
9
|
+
deps do
|
10
|
+
require 'secure_data_bag'
|
11
|
+
end
|
12
|
+
|
13
|
+
option :secret,
|
14
|
+
short: "-s SECRET",
|
15
|
+
long: "--secret",
|
16
|
+
description: "The secret key to use to encrypt data bag item values",
|
17
|
+
proc: Proc.new { |s| Chef::Config[:knife][:secret] = s }
|
18
|
+
|
19
|
+
option :secret_file,
|
20
|
+
long: "--secret-file SECRET_FILE",
|
21
|
+
description: "A file containing a secret key to use to encrypt data bag item values",
|
22
|
+
proc: Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
|
23
|
+
|
24
|
+
option :encode_fields,
|
25
|
+
long: "--encode-fields FIELD1,FIELD2,FIELD3",
|
26
|
+
description: "List of attribute keys for which to encode values",
|
27
|
+
default: Array.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def use_encryption
|
32
|
+
if use_secure_databag then false
|
33
|
+
else
|
34
|
+
if @raw_data["encrypted_data"] or
|
35
|
+
@raw_data.reject { |k,v| k == "id" }.
|
36
|
+
all? { |k,v| v.key? "encrypted_data" }
|
37
|
+
then super
|
38
|
+
else false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def use_secure_databag
|
44
|
+
@raw_data["encryption"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def encoded_fields_for(item)
|
48
|
+
[].concat(config[:encode_fields]).
|
49
|
+
concat(item.encode_fields).
|
50
|
+
uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def require_secret
|
54
|
+
if not config[:secret] and not config[:secret_file]
|
55
|
+
show_usage
|
56
|
+
ui.fatal("A secret or secret_file must be specified")
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
require 'chef/knife/secure_bag_base'
|
3
|
+
require 'chef/knife/data_bag_create'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
class Knife
|
7
|
+
class SecureBagCreate < Knife::DataBagCreate
|
8
|
+
include Knife::SecureBagBase
|
9
|
+
|
10
|
+
banner "knife secure bag create BAG [ITEM] (options)"
|
11
|
+
category "secure bag"
|
12
|
+
|
13
|
+
def create_databag
|
14
|
+
begin
|
15
|
+
rest.post_rest("data", { name: @data_bag_name })
|
16
|
+
ui.info("Created data_bag[#{@data_bag_name}]")
|
17
|
+
rescue Net::HTTPServerException => e
|
18
|
+
raise unless e.to_s =~ /^409/
|
19
|
+
ui.info("Data bag #{@data_bag_name} already exists")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_databag_item
|
24
|
+
create_object({ id: @data_bag_item_name },
|
25
|
+
"data_bag_item[#{@data_bag_item_name}]") do |output|
|
26
|
+
|
27
|
+
@raw_data = output
|
28
|
+
|
29
|
+
item = if use_encryption
|
30
|
+
item = Chef::EncryptedDataBagItem.
|
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)
|
38
|
+
item.data_bag(@data_bag_name)
|
39
|
+
|
40
|
+
rest.post_rest("data/#{@data_bag_name}", item.to_hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
@data_bag_name, @data_bag_item_name = @name_args
|
46
|
+
|
47
|
+
if @data_bag_name.nil?
|
48
|
+
show_usage
|
49
|
+
ui.fatal("You must specify a data bag name")
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
|
53
|
+
require_secret
|
54
|
+
|
55
|
+
begin
|
56
|
+
Chef::DataBag.validate_name!(@data_bag_name)
|
57
|
+
rescue Chef::Exceptions::InvalidDataBagName => e
|
58
|
+
ui.fatal(e.message)
|
59
|
+
exit(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
# create the data bag
|
63
|
+
create_databag
|
64
|
+
|
65
|
+
if @data_bag_item_name
|
66
|
+
create_databag_item
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require 'chef/knife/secure_bag_base'
|
3
|
+
require 'chef/knife/data_bag_edit'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
class Knife
|
7
|
+
class SecureBagEdit < Knife::DataBagEdit
|
8
|
+
include Knife::SecureBagBase
|
9
|
+
|
10
|
+
banner "knife secure bag edit BAG [ITEM] (options)"
|
11
|
+
category "secure bag"
|
12
|
+
|
13
|
+
def load_item(bag, item_name)
|
14
|
+
item = Chef::DataBagItem.load(bag, item_name)
|
15
|
+
@raw_data = item.to_hash
|
16
|
+
|
17
|
+
if use_encryption
|
18
|
+
item = Chef::EncryptedDataBagItem.load(item, read_secret)
|
19
|
+
end
|
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)
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def edit_item(item)
|
32
|
+
output = super
|
33
|
+
|
34
|
+
#
|
35
|
+
# Store the desired fields to encode and set to hash to unencrypted
|
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
|
43
|
+
item.to_hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
require 'chef/knife/secure_bag_base'
|
3
|
+
require 'chef/knife/data_bag_from_file'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
class Knife
|
7
|
+
class SecureBagFromFile < Knife::DataBagFromFile
|
8
|
+
include Knife::SecureBagBase
|
9
|
+
|
10
|
+
deps do
|
11
|
+
require 'chef/data_bag'
|
12
|
+
require 'chef/data_bag_item'
|
13
|
+
require 'chef/knife/core/object_loader'
|
14
|
+
require 'chef/json_compat'
|
15
|
+
require 'chef/encrypted_data_bag_item'
|
16
|
+
require 'secure_data_bag'
|
17
|
+
end
|
18
|
+
|
19
|
+
banner "knife secure bag from file BAG FILE|FLDR [FILE|FLDR] (options)"
|
20
|
+
category "secure bag"
|
21
|
+
|
22
|
+
option :all,
|
23
|
+
short: "-a",
|
24
|
+
long: "--all",
|
25
|
+
description: "Upload all data bags or all items for specified databag"
|
26
|
+
|
27
|
+
def load_data_bag_hash(hash)
|
28
|
+
@raw_data = hash
|
29
|
+
|
30
|
+
if use_encryption
|
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)
|
37
|
+
item.to_hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_data_bag_items(data_bag, items=nil)
|
41
|
+
items ||= find_all_data_bag_items(data_bag)
|
42
|
+
item_paths = normalize_item_paths(items)
|
43
|
+
item_paths.each do |item_path|
|
44
|
+
item = loader.load_from("#{data_bags_path}", data_bag, item_path)
|
45
|
+
item = load_data_bag_hash(item)
|
46
|
+
dbag = Chef::DataBagItem.new
|
47
|
+
dbag.data_bag(data_bag)
|
48
|
+
dbag.raw_data = item
|
49
|
+
dbag.save
|
50
|
+
ui.info("Updated data_bag_item[#{dbag.data_bag}::#{dbag.id}]")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
require 'chef/knife/secure_bag_base'
|
3
|
+
require 'chef/knife/data_bag_show'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
class Knife
|
7
|
+
class SecureBagShow < Knife::DataBagShow
|
8
|
+
include Knife::SecureBagBase
|
9
|
+
|
10
|
+
banner "knife secure bag show BAG [ITEM] (options)"
|
11
|
+
category "secure bag"
|
12
|
+
|
13
|
+
def load_item(bag, item_name)
|
14
|
+
item = Chef::DataBagItem.load(bag, item_name)
|
15
|
+
@raw_data = item.raw_data
|
16
|
+
|
17
|
+
if use_encryption
|
18
|
+
item = Chef::EncryptedDataBagItem.new(@raw_data, read_secret)
|
19
|
+
end
|
20
|
+
|
21
|
+
#item = SecureDataBag::Item.from_item(item, read_secret)
|
22
|
+
item.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
display = case @name_args.length
|
27
|
+
when 2
|
28
|
+
item = load_item(@name_args[0], @name_args[1])
|
29
|
+
display = format_for_display(item)
|
30
|
+
when 1
|
31
|
+
format_list_for_display(Chef::DataBag.load(@name_args[0]))
|
32
|
+
else
|
33
|
+
stdout.puts opt_parser
|
34
|
+
exit(1)
|
35
|
+
end
|
36
|
+
output(display)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/secure_data_bag.rb
CHANGED
@@ -1,30 +1,6 @@
|
|
1
1
|
|
2
|
-
module SecureDataBag
|
3
|
-
end
|
4
|
-
|
5
2
|
require "secure_data_bag/version"
|
6
|
-
require "secure_data_bag/secure_data_bag_item
|
7
|
-
require "secure_data_bag/decryptor
|
8
|
-
require "secure_data_bag/encryptor
|
9
|
-
|
10
|
-
class Chef
|
11
|
-
module DSL
|
12
|
-
module DataQuery
|
13
|
-
def secure_data_bag_item(bag, item)
|
14
|
-
DataBag.validate_name!(bag.to_s)
|
15
|
-
SecureDataBagItem.validate_id!(item)
|
16
|
-
SecureDataBagItem.load(bag, item)
|
17
|
-
rescue Exception
|
18
|
-
Log.error("Failed to load secure data bag item: #{bag.inspect} #{item.inspect}")
|
19
|
-
raise
|
20
|
-
end
|
21
|
-
|
22
|
-
def secure_data_bag_item!(item, fields=[])
|
23
|
-
secure = SecureDataBag::SecureDataBagItem.from_item item
|
24
|
-
secure.encoded_fields secure.encoded_fields + fields
|
25
|
-
secure
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
3
|
+
require "secure_data_bag/secure_data_bag_item"
|
4
|
+
require "secure_data_bag/decryptor"
|
5
|
+
require "secure_data_bag/encryptor"
|
30
6
|
|
@@ -6,29 +6,22 @@ require 'base64'
|
|
6
6
|
require 'digest/sha2'
|
7
7
|
|
8
8
|
module SecureDataBag
|
9
|
-
class
|
9
|
+
class Item
|
10
10
|
class Decryptor
|
11
|
-
attr_reader :key
|
12
|
-
attr_reader :encryption
|
13
|
-
attr_reader :encrypted_hash
|
14
|
-
|
15
11
|
def initialize(encrypted_hash, encryption, key)
|
16
12
|
@encryption = encryption
|
17
13
|
@encrypted_hash = encrypted_hash
|
18
14
|
@key = key
|
15
|
+
@iv = nil
|
19
16
|
end
|
20
17
|
|
21
18
|
def for_decrypted_item
|
22
|
-
pp "decrypted_hash"
|
23
19
|
decrypted_hash
|
24
20
|
end
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
msg << ". Most likely the provided key is incorrect"
|
30
|
-
msg
|
31
|
-
end
|
22
|
+
attr_reader :encrypted_hash
|
23
|
+
attr_reader :encryption
|
24
|
+
attr_reader :key
|
32
25
|
|
33
26
|
def iv
|
34
27
|
@iv ||= begin
|
@@ -46,7 +39,15 @@ module SecureDataBag
|
|
46
39
|
def decrypt_hash(hash)
|
47
40
|
hash.each do |k,v|
|
48
41
|
if encryption[:encoded_fields].include?(k)
|
49
|
-
|
42
|
+
begin
|
43
|
+
v = decrypt_value(v)
|
44
|
+
rescue Yajl::ParseError
|
45
|
+
raise DecryptionFailure,
|
46
|
+
"Error decrypting data bag value for #{k}."
|
47
|
+
rescue OpenSSL::Cipher::CipherError => e
|
48
|
+
raise DecryptionFailure,
|
49
|
+
"Error decrypting data bag value for #{k}: #{e.message}"
|
50
|
+
end
|
50
51
|
elsif v.is_a? Hash
|
51
52
|
v = decrypt_hash(v)
|
52
53
|
end
|
@@ -6,13 +6,8 @@ require 'base64'
|
|
6
6
|
require 'digest/sha2'
|
7
7
|
|
8
8
|
module SecureDataBag
|
9
|
-
class
|
9
|
+
class Item
|
10
10
|
class Encryptor
|
11
|
-
attr_reader :encryption
|
12
|
-
attr_reader :unencrypted_hash
|
13
|
-
attr_reader :encoded_fields
|
14
|
-
attr_reader :key
|
15
|
-
|
16
11
|
def initialize(unencrypted_hash, encryption, key)
|
17
12
|
@encryption = encryption
|
18
13
|
@unencrypted_hash = unencrypted_hash
|
@@ -28,8 +23,12 @@ module SecureDataBag
|
|
28
23
|
data.merge({encryption:encryption_hash})
|
29
24
|
end
|
30
25
|
|
26
|
+
attr_reader :unencrypted_hash
|
27
|
+
attr_reader :encoded_fields
|
28
|
+
attr_reader :encryption
|
29
|
+
attr_reader :key
|
30
|
+
|
31
31
|
def encrypted_hash
|
32
|
-
pp "encrypted_hash"
|
33
32
|
@encrypted_data ||= begin
|
34
33
|
encrypt_hash(unencrypted_hash.dup)
|
35
34
|
end
|
@@ -11,27 +11,21 @@ module SecureDataBag
|
|
11
11
|
# crypto functions, it should be used the same way.
|
12
12
|
#
|
13
13
|
|
14
|
-
class
|
15
|
-
def initialize
|
16
|
-
super
|
17
|
-
|
18
|
-
@secret =
|
19
|
-
@key =
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@algorithm = nil
|
23
|
-
@encoded_fields = []
|
24
|
-
@default_encoded_fields = ["password"]
|
14
|
+
class Item < Chef::DataBagItem
|
15
|
+
def initialize(key=nil)
|
16
|
+
super()
|
17
|
+
|
18
|
+
@secret = Chef::Config[:encrypted_data_bag_secret]
|
19
|
+
@key = key
|
20
|
+
@raw_data = {}
|
21
|
+
@encode_fields = ["password"]
|
25
22
|
end
|
26
23
|
|
27
24
|
#
|
28
|
-
#
|
25
|
+
# Methods for encryption key
|
29
26
|
#
|
30
27
|
|
31
|
-
attr_reader :default_encoded_fields
|
32
|
-
|
33
28
|
def secret(arg=nil)
|
34
|
-
arg ||= Chef::Config[:encrypted_data_bag_secret] if @secret.nil?
|
35
29
|
set_or_return(:secret, arg, kind_of: String)
|
36
30
|
end
|
37
31
|
|
@@ -41,86 +35,123 @@ module SecureDataBag
|
|
41
35
|
end
|
42
36
|
|
43
37
|
def load_key
|
44
|
-
|
45
|
-
|
46
|
-
|
38
|
+
@key = self.class.load_secret(secret)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.load_secret(path=nil)
|
42
|
+
path ||= Chef::Config[:encrypted_data_bag_secret]
|
47
43
|
|
48
|
-
|
49
|
-
|
50
|
-
begin
|
51
|
-
Kernel.open(path).read.strip
|
52
|
-
rescue Errno::ECONNREFUSED
|
53
|
-
raise ArgumentError, "Remove key not available from '#{secret}'"
|
54
|
-
rescue OpenURI::HTTPError
|
55
|
-
raise ArgumentError, "Remove key not found at '#{secret}'"
|
56
|
-
end
|
57
|
-
else
|
58
|
-
unless File.exist?(secret)
|
59
|
-
raise Errno::ENOENT, "file not found '#{secret}'"
|
60
|
-
end
|
61
|
-
IO.read(secret).strip
|
44
|
+
unless path
|
45
|
+
raise ArgumentError, "No secret specified and no secret found."
|
62
46
|
end
|
63
47
|
|
48
|
+
key = case path
|
49
|
+
when /^\w+:\/\// # Remove key
|
50
|
+
begin
|
51
|
+
Kernel.open(path).read.strip
|
52
|
+
rescue Errno::ECONNREFUSED
|
53
|
+
raise ArgumentError, "Remove key not available from '#{path}'"
|
54
|
+
rescue OpenURI::HTTPError
|
55
|
+
raise ArgumentError, "Remove key not found at '#{path}'"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
unless File.exist?(path)
|
59
|
+
raise Errno::ENOENT, "file not found '#{path}'"
|
60
|
+
end
|
61
|
+
IO.read(path).strip
|
62
|
+
end
|
63
|
+
|
64
64
|
if key.size < 1
|
65
|
-
raise ArgumentError, "invalid zero length
|
65
|
+
raise ArgumentError, "invalid zero length path in '#{path}'"
|
66
66
|
end
|
67
|
+
|
67
68
|
key
|
68
69
|
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
#
|
72
|
+
# Wrapper for raw_data encryption settings
|
73
|
+
# - always ensure that encryption hash is present
|
74
|
+
# - always ensure encryption settings have defaults
|
75
|
+
#
|
74
76
|
|
75
|
-
def
|
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
|
77
85
|
end
|
78
86
|
|
79
87
|
#
|
80
|
-
#
|
81
|
-
# we
|
88
|
+
# Setter for @raw_data
|
89
|
+
# - ensure the data we receive is a Mash to support symbols
|
90
|
+
# - pass it to DataBagItem for additional validation
|
91
|
+
# - ensure the data has the encryption hash
|
92
|
+
# - decode the data
|
82
93
|
#
|
83
94
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
95
|
+
def raw_data=(data)
|
96
|
+
data = Mash.new(data)
|
97
|
+
super data
|
98
|
+
encryption
|
99
|
+
decode_data!
|
87
100
|
end
|
88
101
|
|
89
102
|
#
|
90
|
-
#
|
103
|
+
# Determine whether the data is encoded or not
|
104
|
+
# - yeah, it's pretty naive
|
91
105
|
#
|
92
106
|
|
93
|
-
def
|
94
|
-
|
95
|
-
iv: iv,
|
96
|
-
cipher: cipher,
|
97
|
-
encoded_fields: encoded_fields + default_encoded_fields
|
98
|
-
}
|
107
|
+
def encoded?
|
108
|
+
not encryption[:encoded_fields].empty?
|
99
109
|
end
|
100
110
|
|
101
|
-
|
102
|
-
|
103
|
-
|
111
|
+
#
|
112
|
+
# Fields we wish to encode
|
113
|
+
# - this differs from encryption[:encoded_fields] and will get merged
|
114
|
+
# into the latter upon an encode
|
115
|
+
#
|
116
|
+
|
117
|
+
def encode_fields(arg=nil)
|
118
|
+
arg = Array(arg).uniq if arg
|
119
|
+
set_or_return(:encode_fields, arg, kind_of: Array).uniq
|
104
120
|
end
|
105
121
|
|
106
122
|
#
|
107
123
|
# Encoder / Decoder
|
108
124
|
#
|
109
125
|
|
110
|
-
def decode_data
|
111
|
-
|
112
|
-
|
126
|
+
def decode_data!
|
127
|
+
#
|
128
|
+
# Ensure that we save previously encoded fields into our list of fields
|
129
|
+
# we wish to encode next time
|
130
|
+
#
|
131
|
+
encode_fields.concat(encryption[:encoded_fields]).uniq!
|
132
|
+
@raw_data = decoded_data if encoded?
|
133
|
+
@raw_data
|
134
|
+
end
|
113
135
|
|
114
|
-
|
115
|
-
|
116
|
-
|
136
|
+
def decoded_data
|
137
|
+
data = Decryptor.new(raw_data, encryption, key).for_decrypted_item
|
138
|
+
data[:encryption][:encoded_fields] = []
|
139
|
+
data
|
140
|
+
end
|
117
141
|
|
118
|
-
|
119
|
-
|
120
|
-
@raw_data
|
142
|
+
def encode_data!
|
143
|
+
@raw_data = encoded_data
|
121
144
|
end
|
122
145
|
|
123
|
-
def
|
146
|
+
def encoded_data
|
147
|
+
#
|
148
|
+
# When encoding data we'll merge those fields already encoded during
|
149
|
+
# the previous state, found in encryption[:encoded_fields] with those
|
150
|
+
# which we wish to encode
|
151
|
+
#
|
152
|
+
encryption = self.encryption.dup
|
153
|
+
encryption[:encoded_fields] = @encode_fields.
|
154
|
+
concat(encryption[:encoded_fields]).uniq
|
124
155
|
Encryptor.new(raw_data, encryption, key).for_encrypted_item
|
125
156
|
end
|
126
157
|
|
@@ -128,21 +159,20 @@ module SecureDataBag
|
|
128
159
|
# Transitions
|
129
160
|
#
|
130
161
|
|
131
|
-
def self.from_hash(h)
|
132
|
-
|
133
|
-
item =
|
134
|
-
item.raw_data = m
|
162
|
+
def self.from_hash(h, key=nil)
|
163
|
+
item = new(key)
|
164
|
+
item.raw_data = h
|
135
165
|
item
|
136
166
|
end
|
137
167
|
|
138
|
-
def self.from_item(h)
|
139
|
-
item = self.from_hash(h.to_hash)
|
168
|
+
def self.from_item(h, key=nil)
|
169
|
+
item = self.from_hash(h.to_hash, key)
|
140
170
|
item.data_bag h.data_bag
|
141
171
|
item
|
142
172
|
end
|
143
173
|
|
144
|
-
def to_hash
|
145
|
-
result =
|
174
|
+
def to_hash(encoded = true)
|
175
|
+
result = encoded ? encoded_data : decoded_data
|
146
176
|
result["chef_type"] = "data_bag_item"
|
147
177
|
result["data_bag"] = self.data_bag
|
148
178
|
result
|
@@ -154,7 +184,7 @@ module SecureDataBag
|
|
154
184
|
json_class: "Chef::DataBagItem",
|
155
185
|
chef_type: "data_bag_item",
|
156
186
|
data_bag: self.data_bag,
|
157
|
-
raw_data:
|
187
|
+
raw_data: encoded_data
|
158
188
|
}
|
159
189
|
result.to_json(*a)
|
160
190
|
end
|
data/secure_data_bag.gemspec
CHANGED
@@ -8,16 +8,19 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = SecureDataBag::VERSION
|
9
9
|
spec.authors = ["Jonathan Serafini"]
|
10
10
|
spec.email = ["jonathan@lightspeedretail.com"]
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage = ""
|
11
|
+
spec.summary = "Per-field data bag item encryption"
|
12
|
+
spec.description = "Provides a mechanism to partially encrypt data bag items and therefore ensure that they are searchable"
|
14
13
|
spec.license = "MIT"
|
15
|
-
spec.homepage =
|
14
|
+
spec.homepage =
|
15
|
+
"https://github.com/JonathanSerafini/chef-secure_data_bag"
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
|
21
|
+
spec.add_dependency 'chef'
|
22
|
+
spec.add_dependency 'yajl-ruby'
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
25
|
spec.add_development_dependency "rake"
|
23
26
|
end
|
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secure_data_bag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
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-08-
|
11
|
+
date: 2014-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: chef
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yajl-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
13
55
|
- !ruby/object:Gem::Dependency
|
14
56
|
name: rake
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -37,13 +79,19 @@ files:
|
|
37
79
|
- LICENSE.txt
|
38
80
|
- README.md
|
39
81
|
- Rakefile
|
82
|
+
- lib/chef/dsl/data_query.rb
|
83
|
+
- lib/chef/knife/secure_bag_base.rb
|
84
|
+
- lib/chef/knife/secure_bag_create.rb
|
85
|
+
- lib/chef/knife/secure_bag_edit.rb
|
86
|
+
- lib/chef/knife/secure_bag_from_file.rb
|
87
|
+
- lib/chef/knife/secure_bag_show.rb
|
40
88
|
- lib/secure_data_bag.rb
|
41
89
|
- lib/secure_data_bag/decryptor.rb
|
42
90
|
- lib/secure_data_bag/encryptor.rb
|
43
91
|
- lib/secure_data_bag/secure_data_bag_item.rb
|
44
92
|
- lib/secure_data_bag/version.rb
|
45
93
|
- secure_data_bag.gemspec
|
46
|
-
homepage: https://github.com/
|
94
|
+
homepage: https://github.com/JonathanSerafini/chef-secure_data_bag
|
47
95
|
licenses:
|
48
96
|
- MIT
|
49
97
|
metadata: {}
|
@@ -66,5 +114,5 @@ rubyforge_project:
|
|
66
114
|
rubygems_version: 2.1.11
|
67
115
|
signing_key:
|
68
116
|
specification_version: 4
|
69
|
-
summary:
|
117
|
+
summary: Per-field data bag item encryption
|
70
118
|
test_files: []
|