1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
#
# Author:: Seth Falcon (<seth@chef.io>)
# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require_relative "config"
Chef.autoload :DataBagItem, File.expand_path("data_bag_item", __dir__)
require_relative "encrypted_data_bag_item/decryptor"
require_relative "encrypted_data_bag_item/encryptor"
require_relative "dist"
# An EncryptedDataBagItem represents a read-only data bag item where
# all values, except for the value associated with the id key, have
# been encrypted.
#
# EncryptedDataBagItem can be used in recipes to decrypt data bag item
# members.
#
# Data bag item values are assumed to have been encrypted using the
# default symmetric encryption provided by Encryptor.encrypt where
# values are converted to YAML prior to encryption.
#
# If the shared secret is not specified at initialization or load,
# then the contents of the file referred to in
# Chef::Config[:encrypted_data_bag_secret] will be used as the
# secret. The default path is /etc/chef/encrypted_data_bag_secret
#
# EncryptedDataBagItem is intended to provide a means to avoid storing
# data bag items in the clear on the Chef server. This provides some
# protection against a breach of the Chef server or of Chef server
# backup data. Because the secret must be stored in the clear on any
# node needing access to an EncryptedDataBagItem, this approach
# provides no protection of data bag items from actors with access to
# such nodes in the infrastructure.
#
class Chef::EncryptedDataBagItem
ALGORITHM = "aes-256-cbc".freeze
AEAD_ALGORITHM = "aes-256-gcm".freeze
#
# === Synopsis
#
# EncryptedDataBagItem.new(hash, secret)
#
# === Args
#
# +enc_hash+::
# The encrypted hash to be decrypted
# +secret+::
# The raw secret key
#
# === Description
#
# Create a new encrypted data bag item for reading (decryption)
#
def initialize(enc_hash, secret)
@enc_hash = enc_hash
@secret = secret
end
def [](key)
value = @enc_hash[key]
if key == "id" || value.nil?
value
else
Decryptor.for(value, @secret).for_decrypted_item
end
end
def []=(key, value)
raise ArgumentError, "assignment not supported for #{self.class}"
end
def to_h
@enc_hash.keys.inject({}) { |hash, key| hash[key] = self[key]; hash }
end
alias_method :to_hash, :to_h
def self.encrypt_data_bag_item(plain_hash, secret)
plain_hash.inject({}) do |h, (key, val)|
h[key] = if key != "id"
Encryptor.new(val, secret).for_encrypted_item
else
val
end
h
end
end
#
# === Synopsis
#
# EncryptedDataBagItem.load(data_bag, name, secret = nil)
#
# === Args
#
# +data_bag+::
# The name of the data bag to fetch
# +name+::
# The name of the data bag item to fetch
# +secret+::
# The raw secret key. If the +secret+ is nil, the value of the file at
# +Chef::Config[:encrypted_data_bag_secret]+ is loaded. See +load_secret+
# for more information.
#
# === Description
#
# Loads and decrypts the data bag item with the given name.
#
def self.load(data_bag, name, secret = nil)
raw_hash = Chef::DataBagItem.load(data_bag, name)
secret ||= load_secret
new(raw_hash, secret)
end
def self.load_secret(path = nil)
require "open-uri" unless defined?(OpenURI)
path ||= Chef::Config[:encrypted_data_bag_secret]
unless path
raise ArgumentError, "No secret specified and no secret found at #{Chef::Config.platform_specific_path(Chef::Dist::CONF_DIR + "/encrypted_data_bag_secret")}"
end
secret = case path
when %r{^\w+://}
# We have a remote key
begin
Kernel.open(path).read.strip
rescue Errno::ECONNREFUSED
raise ArgumentError, "Remote key not available from '#{path}'"
rescue OpenURI::HTTPError
raise ArgumentError, "Remote key not found at '#{path}'"
end
else
unless File.exist?(path)
raise Errno::ENOENT, "file not found '#{path}'"
end
IO.read(path).strip
end
if secret.size < 1
raise ArgumentError, "invalid zero length secret in '#{path}'"
end
secret
end
end
|