Skip to content

zuazo/chef-encrypted-attributes

Repository files navigation

Chef-Encrypted-Attributes

Gem Version GitHub License

Dependency Status Code Climate Build Status Coverage Status Inline docs

Chef plugin to add Node encrypted attributes support using client keys.

We recommend using the encrypted_attributes cookbook for easy installation.

Description

Node attributes are encrypted using chef client and user keys with public key infrastructure (PKI). You can choose which clients, nodes or users will be able to read the attribute.

Chef Nodes with read access can be specified using a node_search query. In case new nodes are added or removed, the data will be re-encrypted in the next Chef Run of the encrypting node (using the #update method shown below). Similarly, a client_search query can be used to allow Chef Clients to read the attribute.

Requirements

  • Ruby >= 2.0
  • Chef Client ~> 11.8
  • yajl_ruby ~> 1.1 or ffi_yajl >= 1.0, <3.0 (included with Chef)
  • If you want to use protocol version 2 to use GCM (disabled by default):
  • Ruby >= 2.
  • OpenSSL >= 1.0.1.

Usage in Recipes

Before reading all the documentation below, we recommend you take a look at the encrypted_attributes cookbook's helper libraries. Those libraries are easier to use than the underlying API and cover the most common use cases.

Installing and Including the Gem

You need to install and include the chef-encrypted-attributes gem before using encrypted attributes inside a cookbook.

chef_gem 'chef-encrypted-attributes'
require 'chef/encrypted_attributes'

Typical Example

In the following example we save a simple FTP user password.

chef_gem 'chef-encrypted-attributes'
require 'chef/encrypted_attributes'

# include the #secure_password method
Chef::Recipe.send(:include, Opscode::OpenSSL::Password)

if Chef::EncryptedAttribute.exist?(node['myapp']['ftp_password'])
  # update with the new keys
  Chef::EncryptedAttribute.update(node.set['myapp']['ftp_password'])

  # read the password
  ftp_pass = Chef::EncryptedAttribute.load(node['myapp']['ftp_password'])
else
  # create the password and save it
  ftp_pass = secure_password
  node.set['myapp']['ftp_password'] = Chef::EncryptedAttribute.create(ftp_pass)
end

# use `ftp_pass` for something here ...

Note: This example requires the openssl cookbook.

Minimal Write Only Example

In this example we only need to save some data from the local node and read it from another:

chef_gem 'chef-encrypted-attributes'
require 'chef/encrypted_attributes'

# Allow all admin clients to read the attributes encrypted by me
Chef::Config[:encrypted_attributes][:client_search] = 'admin:true'

# Allow all webapp nodes to read the attributes encrypted by me
Chef::Config[:encrypted_attributes][:node_search] = 'role:webapp'

if Chef::EncryptedAttribute.exist?(node['myapp']['encrypted_data'])
  # we can used #load here as above if we need the `encrypted_data` outside
  # this `if`

  # update with the new keys
  Chef::EncryptedAttribute.update(node.set['myapp']['encrypted_data'])
else
  # create the data, encrypt and save it
  data_to_encrypt = # ....
  node.set['myapp']['encrypted_data'] =
    Chef::EncryptedAttribute.create(data_to_encrypt)
end

Then we can read this attribute from another allowed node (a 'role:webapp' node):

include_recipe 'encrypted_attributes'
# Expose the public key for encryption
include_recipe 'encrypted_attributes::expose_key'

if Chef::EncryptedAttribute.exist_on_node?(
     'random.example.com', %w(myapp encrypted_data)
   )
  data = Chef::EncryptedAttribute.load_from_node(
    'random.example.com', %w(myapp encrypted_data)
  )

  # use `data` for something here ...
end

Note: Be careful when using #exist_on_node? and #load_from_node and remember passing the attribute path to read as Array of Strings instead of using node[...] (which points to the local node).

Example Using User Keys Data Bag

Suppose we want to store users public keys in a data bag and give them access to the attributes. This can be a workaround for the Chef Users Limitation problem.

You need to create a Data Bag Item with a content similar to the following:

{
  "id": "chef_users",
  "bob": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFA...",
  "alice": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFA..."
}

This data bag will contain the user public keys retrieved with knife user show USER -a public_key -f json.

Then, from a recipe, you can read this user keys and allow them to read the attributes.

chef_gem 'chef-encrypted-attributes'
require 'chef/encrypted_attributes'

chef_users = Chef::DataBagItem.load('global_data_bag', 'chef_users')
# remove the data bag "id" to avoid to confuse it with a user:
chef_users.delete('id')

Chef::Log.debug(
  "Chef Users able to read the Encrypted Attributes: #{chef_users.keys.inspect}"
)
Chef::Config[:encrypted_attributes][:keys] = chef_users.values

# if Chef::EncryptedAttribute.exist?(...)
#   Chef::EncryptedAttribute.update(...)
# else
#   node.set[...][...] = Chef::EncryptedAttribute.create(...)
# ...

Note: This data bag does not need to be encrypted, because it only stores public keys.

Chef::EncryptedAttribute API

See the API documentation for a more detailed information about Chef::EncryptedAttribute class and its methods.

Chef User Keys Access Limitation

Keep in mind that, from a Chef Node, Chef User public keys are inaccessible. So you have to pass them in raw mode in the recipe if you need any Chef User to be able to use the encrypted attributes (this is required for example to use the knife commands included in this gem, as knife is usually used by Chef Users). Summarizing, Chef Node inside a recipe (using its Chef Client key) will not be able to retrieve the Chef Users public keys, so you need to pass them using the [:keys] configuration value.

Chef Nodes (Clients) with admin privileges do have access to user public keys, but in most cases this is not a recommended practice.

See the Example Using User Keys Data Bag section for a workaround. You can use the encrypted_attributes::users_data_bag recipe for this.

Note: Chef Clients usually are Chef Nodes and chef-validation/chef-webui keys. Chef Users usually are knife users. The main difference between Chef Users and Chef Clients is that the former are able to log in via web-ui (has a password).

Chef Client Keys Access Limitation

Chef Client public keys has a similar problem to the user keys, you cannot retrieve them from a Chef Node.

To fix this limitation you should expose de Chef Client public key in the node['public_key'] attribute. You can include the encrypted_attributes::expose_key recipe for this. You need to include this recipe in the Chef Nodes that require read privileges on the encrypted attributes.

Exposing the public key through attributes should not be considered a security breach, so it's not a problem to include it on all machines.

Maximum Number of Nodes

This gem is ready to be used with Chef Servers that have less than 1000 nodes by default. You can increase this limit setting the search_max_rows configuration option:

Chef::Config[:encrypted_attributes][:search_max_rows] = 50_000

Knife Commands

See the KNIFE.md file.

Internal Low Level Documentation

The cryptographic systems used are documented in the following classes:

See the official gem documentation for more information.

Using Signed Gems

The chef-encrypted-attributes gem is cryptographically signed by Onddo Labs's certificate, which identifies as xabier@zuazo.org. You can obtain the official signature here:

https://raw.github.com/zuazo/chef-encrypted-attributes/master/certs/xabier_zuazo.crt

To be sure the gem you install has not been tampered with:

$ gem cert --add <(curl -Ls https://raw.github.com/zuazo/chef-encrypted-attributes/master/certs/xabier_zuazo.crt)
$ gem install chef-encrypted-attributes -P MediumSecurity

The MediumSecurity trust profile will verify signed gems, but allow the installation of unsigned dependencies. This is necessary because not all of chef-encrypted-attributes's dependencies are signed, so we cannot use HighSecurity.

We recommend to remove our certificate after the gem has been successfully verified and installed:

$ gem cert --remove '/cn=xabier/dc=zuazo/dc=org'

Security Notes

All the cryptographic systems and algorithms used by chef-encrypted-attributes are carefully described in the internal documentation for public review. The code was originally based on Encrypted Data Bags and chef-vault implementations, then improved.

Still, this gem should be considered experimental until audited by professional cryptographers.

Reporting Security Problems

If you have discovered a bug in chef-encrypted-attributes of a sensitive nature, i.e. one which can compromise the security of chef-encrypted-attributes users, you can report it securely by sending a GPG encrypted message. Please use the following key:

https://raw.github.com/zuazo/chef-encrypted-attributes/master/zuazo.gpg

The key fingerprint is (or should be):

ADAE EEFC BD78 6CBB B76B  1662 2195 FF19 5324 14AB

Testing

See TESTING.md.

Contributing

Please do not hesitate to open an issue with any questions or problems.

See CONTRIBUTING.md.

TODO

See TODO.md.

License and Author

Author: Xabier de Zuazo (xabier@zuazo.org)
Contributor: Josh Kalderimis
Contributor: Crystal Hsiung
Contributor: Lisa Danz
Contributor: Eric Blevins
Copyright: Copyright (c) 2016 Xabier de Zuazo
Copyright: Copyright (c) 2014-2015 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.