secure_data_bag 1.0.5 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|