lib/chef/encrypted_attribute/config.rb
# encoding: UTF-8
#
# Author:: Xabier de Zuazo (<xabier@zuazo.org>)
# Copyright:: Copyright (c) 2014 Onddo Labs, SL.
# 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 'chef/mixin/params_validate'
class Chef
class EncryptedAttribute
# Encrypted attributes configuration options object.
class Config
include ::Chef::Mixin::ParamsValidate
# Returns configuration options list.
#
# @api private
OPTIONS = [
:version,
:partial_search,
:client_search,
:search_max_rows,
:node_search,
:users,
:keys
].freeze
# Constructs a {Config} object.
#
# @param config [Config, Hash] configuration object to clone.
def initialize(config = nil)
update!(config) unless config.nil?
end
# Reads or sets Encrypted Mash protocol version.
#
# @param arg [String, Fixnum] protocol version to use. Must be a number.
# @return [Fixnum] protocol version.
def version(arg = nil)
unless arg.nil? || !arg.is_a?(String)
begin
arg = Integer(arg)
rescue ArgumentError
arg
end
end
set_or_return(:version, arg, kind_of: [Fixnum, String], default: 1)
end
# Reads or sets partial search support.
#
# Set it to `false` to disable partial search. Defaults to `true`.
#
# @param arg [Boolean] whether to enable partial search.
# @return [Boolean] partial search usage.
# @see
# http://docs.chef.io/chef_search.html Chef Search documentation
def partial_search(arg = nil)
set_or_return(
:partial_search, arg, kind_of: [TrueClass, FalseClass], default: true
)
end
# Reads or sets client search query.
#
# This query will return a list of clients that will be able to read the
# encrypted attribute.
#
# @param arg [String, Array<String>] list of client queries to perform.
# @return [Array<String>] list of client queries.
# @see
# http://docs.chef.io/chef_search.html Chef Search documentation
def client_search(arg = nil)
set_or_return_search_array(:client_search, arg)
end
# Set the maximum number of rows to be returned by internal search
# functions.
#
# You must set this value to your maximum number of nodes in your Chef
# Server. Defaults to `1000`.
#
# @param arg [Integer] maximum rows number.
# @return [Integer] maximum rows number.
def search_max_rows(arg = nil)
set_or_return(
:search_max_rows, arg, kind_of: Integer, default: 1000
)
end
# Reads or sets node search query.
#
# This query will return a list of nodes that will be able to read the
# encrypted attribute.
#
# @param arg [String, Array<String>] list of node queries to perform.
# @return [Array<String>] list of node queries.
def node_search(arg = nil)
set_or_return_search_array(:node_search, arg)
end
# Reads or sets user list.
#
# This contains the user list that will be able to read the encrypted
# attribute.
#
# @param arg [String, Array<String>] list of users to set.
# @return [Array<String>] list of users.
def users(arg = nil)
set_or_return(
:users, arg,
kind_of: [String, Array], default: [],
callbacks: config_users_arg_callbacks
)
end
# Reads or sets key list.
#
# This contains the raw key list that will be able to read the encrypted
# attribute.
#
# @param arg [Array<String, OpenSSL::PKey::RSA>] the keys in PEM format.
# @return [Array<String, OpenSSL::PKey::RSA>] the keys in PEM format
def keys(arg = nil)
set_or_return(
:keys, arg,
kind_of: Array, default: [],
callbacks: config_valid_keys_array_callbacks
)
end
# Replaces the current config.
#
# When setting using a {Chef::EncryptedAttribute::Config} class, all the
# configuration options will be replaced.
#
# When setting using a _Hash_, only the provided keys will be replaced.
#
# @param config [Config, Hash] the configuration to set.
# @return [Config] `self`.
def update!(config)
if config.is_a?(self.class)
update_from_config!(config)
elsif config.is_a?(Hash)
update_from_hash!(config)
end
end
# Reads a configuration option.
#
# @param key [String, Symbol] configuration option to read.
# @return [Mixed] configuration value.
def [](key)
key = key.to_sym if key.is_a?(String)
send(key) if OPTIONS.include?(key)
end
# Sets a configuration option.
#
# @param key [String, Symbol] configuration option name to set.
# @param value [Mixed] configuration value to set.
# @return [Mixed] configuration value.
def []=(key, value)
key = key.to_sym if key.is_a?(String)
send(key, value) if OPTIONS.include?(key)
end
protected
# Duplicates an object avoiding Ruby exceptions if not supported.
#
# @param o [Object] object to duplicate.
# @return [Object] duplicated object.
def dup_object(o)
o.dup
rescue TypeError
o
end
# Creates getter and setter method for **search array** configuration
# options.
#
# This configuration options contains an array of search queries.
#
# @param name [Symbol] configuration option name.
# @param arg [Array<String>, String] configuration option value to set.
# @return [Array<String>] configuration option value.
def set_or_return_search_array(name, arg = nil)
arg = [arg] unless arg.nil? || !arg.is_a?(String)
set_or_return(
name, arg,
kind_of: Array, default: [], callbacks: config_search_array_callbacks
)
end
# Checks a search query array list.
#
# @param s_ary [Array<String>] search query array.
# @return [Boolean] `true` if the search query list is in the correct
# format.
def config_valid_search_array?(s_ary)
s_ary.each do |s|
return false unless s.is_a?(String)
end
true
end
# Returns configuration option callback function for search arrays.
#
# @return [Proc] search arrays checking callback function.
def config_search_array_callbacks
{
'should be a valid array of search patterns' => lambda do |cs|
config_valid_search_array?(cs)
end
}
end
# Checks a user list option value.
#
# @param users [Array<String>, '*'] user list to check.
# @return [Boolean] `true` if the user list is in the correct
# format.
def config_valid_user_arg?(users)
return users == '*' if users.is_a?(String)
users.each do |u|
return false unless u.is_a?(String) && u.match(/^[a-z0-9\-_]+$/)
end
true
end
# Returns configuration option callback function for user lists.
#
# @return [Proc] user lists checking callback function.
def config_users_arg_callbacks
{
'should be a valid array of search patterns' => lambda do |us|
config_valid_user_arg?(us)
end
}
end
# Checks if an OpenSSL key is in the correct format.
#
# Only checks that has a public key. It may lack private key.
#
# @param k [String, OpenSSL::PKey::RSA] key to check.
# @return [Boolean] `true` if the public key is correct.
def config_valid_key?(k)
rsa_k =
case k
when OpenSSL::PKey::RSA then k
when String
begin
OpenSSL::PKey::RSA.new(k)
rescue OpenSSL::PKey::RSAError, TypeError
nil
end
end
return false if rsa_k.nil?
rsa_k.public?
end
# Checks if an OpenSSL key array is in the correct format.
#
# Only checks that the keys have a public key. They may lack private key.
#
# @param k_ary [Array<String, OpenSSL::PKey::RSA>] array of keys to check.
# @return [Boolean] `true` if the public keys are all correct.
def config_valid_keys_array?(k_ary)
k_ary.each do |k|
return false unless config_valid_key?(k)
end
true
end
# Returns configuration option callback function for public keys.
#
# @return [Proc] public keys checking callback function.
def config_valid_keys_array_callbacks
{
'should be a valid array of keys' => lambda do |keys|
config_valid_keys_array?(keys)
end
}
end
# Copies a configuration. All the current configuration options will be
# replaced.
#
# Called by {#update_from!} for {Config} objects.
#
# @param config [Config] configuration options to copy.
def update_from_config!(config)
OPTIONS.each do |attr|
value = dup_object(config.send(attr))
instance_variable_set("@#{attr}", value)
end
end
# Copies a configuration option. Only the provided Hash keys will be
# replaced, the others will be preserved.
#
# Called by {#update_from!} for *Hash* objects.
#
# @param config [Hash] configuration options to copy.
def update_from_hash!(config)
config.each do |attr, value|
attr = attr.to_sym if attr.is_a?(String)
if OPTIONS.include?(attr)
value = dup_object(value)
send(attr, value)
else
Chef::Log.warn(
"#{self.class}: configuration method not found: "\
"#{attr.to_s.inspect}."
)
end
end
end
end
end
end