Coder Social home page Coder Social logo

zuazo / ssl_certificate-cookbook Goto Github PK

View Code? Open in Web Editor NEW
32.0 4.0 36.0 487 KB

Chef cookbook to make it easy for other cookbooks to support SSL.

Home Page: https://supermarket.chef.io/cookbooks/ssl_certificate

License: Apache License 2.0

Ruby 88.61% HTML 2.70% Shell 8.69%
chef devops ssl certificate tls security cookbook

ssl_certificate-cookbook's Introduction

SSL Certificate Cookbook

GitHub License

Cookbook Version Dependency Status Code Climate Build Status Circle CI

The main purpose of this Chef cookbook is to make it easy for other cookbooks to support SSL. With the resource included, you will be able to manage certificates reading them from attributes, data bags or chef-vaults. Exposing its configuration through node attributes.

Much of the code in this cookbook is heavily based on the SSL implementation from the owncloud cookbook.

Table of Contents

Requirements

Supported Platforms

This cookbook has been tested on the following platforms:

  • Amazon Linux
  • CentOS
  • Debian
  • Fedora
  • FreeBSD
  • Oracle Linux
  • RedHat
  • Scientific Linux
  • Ubuntu
  • Windows

Please, let us know if you use it successfully on any other platform.

Required Applications

  • Chef 12 or higher.
  • Ruby 2.3 or higher.

Usage

Including the Cookbook

You need to include this recipe in your run_list before using the ssl_certificate resource:

{
  "name": "example.com",
  "[...]": "[...]",
  "run_list": [
    "recipe[ssl_certificate]"
  ]
}

You can also include the cookbook as a dependency in the metadata of your cookbook:

# metadata.rb
depends 'ssl_certificate'

One of the two is enough. No need to do anything else. Only use the ssl_certificate resource to create the certificates you need.

A Short Example

cert = ssl_certificate 'webapp1' do
  namespace node['webapp1'] # optional but recommended
end
# you can now use the #cert_path and #key_path methods to use in your
# web/mail/ftp service configurations
log "WebApp1 certificate is here: #{cert.cert_path}"
log "WebApp1 private key is here: #{cert.key_path}"

Namespaces

The ssl_certificate resource namespace parameter is a node attribute path, like for example node['example.com'], used to configure SSL certificate defaults. This will make easier to integrate the node attributes with the certificate creation matters. This means you can configure the certificate creation through node attributes.

When a namespace is set in the resource, it will try to read the following attributes below the namespace (all attributes are optional):

Attribute Description
namespace['common_name'] Server name or Common Name, used for self-signed certificates (uses node['fqdn'] by default).
namespace['country'] Country, used for self-signed certificates.
namespace['city'] City, used for self-signed certificates.
namespace['state'] State or Province name, used for self-signed certificates.
namespace['organization'] Organization or Company name, used for self-signed certificates.
namespace['department'] Department or Organizational Unit, used for self-signed certificates.
namespace['email'] Email address, used for self-signed certificates.
namespace['source'] Attribute for setting certificate source and key source (both) to a value (key_source and cert_source).
namespace['bag'] Attribute for setting certificate bag and key bag (both) to a value (key_bag and cert_bag).
namespace['item'] Attribute for setting certificate item name and key bag item name (both) to a value (key_item and cert_item).
namespace['encrypted'] Attribute for setting certificate encryption and key encryption (both) to a value (key_encryption and cert_encryption).
namespace['secret_file'] Attribute for setting certificate chef secret file and key chef secret file (both) to a value (key_secret_file and cert_secret_file).
namespace['ssl_key']['source'] Source type to get the SSL key from. Can be 'self-signed', 'attribute', 'data-bag', 'chef-vault' or 'file'.
namespace['ssl_key']['path'] File path of the SSL key.
namespace['ssl_key']['mode'] File mode of the SSL key.
namespace['ssl_key']['bag'] Name of the Data Bag where the SSL key is stored.
namespace['ssl_key']['item'] Name of the Data Bag Item where the SSL key is stored.
namespace['ssl_key']['item_key'] Key of the Data Bag Item where the SSL key is stored.
namespace['ssl_key']['encrypted'] Whether the Data Bag where the SSL key is stored is encrypted.
namespace['ssl_key']['secret_file'] Secret file used to decrypt the Data Bag where the SSL key is stored.
namespace['ssl_key']['content'] SSL key content used when reading from attributes.
namespace['ssl_key']['length'] RSA key length used when generating a new key.
namespace['ssl_cert']['source'] Source type to get the SSL cert from. Can be 'self-signed', 'attribute', 'data-bag', 'chef-vault' or 'file'.
namespace['ssl_cert']['path'] File path of the SSL certificate.
namespace['ssl_cert']['bag'] Name of the Data Bag where the SSL cert is stored.
namespace['ssl_cert']['item'] Name of the Data Bag Item where the SSL cert is stored.
namespace['ssl_cert']['item_key'] Key of the Data Bag Item where the SSL cert is stored.
namespace['ssl_cert']['encrypted'] Whether the Data Bag where the SSL cert is stored is encrypted.
namespace['ssl_cert']['secret_file'] Secret file used to decrypt the Data Bag where the SSL cert is stored.
namespace['ssl_cert']['content'] SSL cert content used when reading from attributes.
namespace['ssl_cert']['subject_alternate_names'] An array of Subject Alternate Names for the SSL cert. Needed if your site has multiple domain names on the same cert.
namespace['ssl_cert']['extended_key_usage'] An array of extended key usage attributes.
namespace['ssl_chain']['name'] File name to be used for the intermediate certificate chain file. If this is not present, no chain file will be written.
namespace['ssl_chain']['source'] Source type to get the intermediate certificate chain from. Can be 'attribute', 'data-bag', 'chef-vault' or 'file'.
namespace['ssl_chain']['path'] File path of the intermediate SSL certificate chain.
namespace['ssl_chain']['combined_path'] File path of the combined certificates (intermediate chain + domain certificate).
namespace['ssl_chain']['bag'] Name of the Data Bag where the intermediate certificate chain is stored.
namespace['ssl_chain']['item'] Name of the Data Bag Item where the intermediate certificate chain is stored.
namespace['ssl_chain']['item_key'] Key of the Data Bag Item where the intermediate certificate chain is stored.
namespace['ssl_chain']['encrypted'] Whether the Data Bag where the intermediate certificate chain is stored is encrypted.
namespace['ssl_chain']['secret_file'] Secret file used to decrypt the Data Bag where the intermediate certificate chain is stored.
namespace['ssl_chain']['content'] Intermediate certificate chain content used when reading from attributes.
namespace['ca_cert_path'] Certificate Authority full path.
namespace['ca_key_path'] Key Authority full path.
namespace['ca_key_passphrase'] Key Authority passphrase.
namespace['pkcs12_path'] Optional PKCS12 full path.
namespace['pkcs12_passphrase'] Optional PKCS12 passphrase.

Examples

Apache Examples

Apache web_app example using community apache2 cookbook and node attributes:

node.default['my-webapp']['common_name'] = 'example.com'
node.default['my-webapp']['ssl_cert']['source'] = 'self-signed'
node.default['my-webapp']['ssl_key']['source'] = 'self-signed'

# we need to save the resource variable to get the key and certificate file
# paths
cert = ssl_certificate 'my-webapp' do
  # we want to be able to use node['my-webapp'] to configure the certificate
  namespace node['my-webapp']
  notifies :restart, 'service[apache2]'
end

include_recipe 'apache2'
include_recipe 'apache2::mod_ssl'
web_app 'my-webapp' do
  # this cookbook includes a virtualhost template for apache2
  cookbook 'ssl_certificate'
  server_name cert.common_name
  docroot # [...]
  # [...]
  ssl_key cert.key_path
  ssl_cert cert.cert_path
  ssl_chain cert.chain_path
end

Using custom paths:

my_key_path = '/etc/keys/my-webapp.key'
my_cert_path = '/etc/certs/my-webapp.pem'

# there is no need to save the resource in a variable in this case because we
# know the paths
ssl_certificate 'my-webapp' do
  key_path my_key_path
  key_mode 00640
  cert_path my_cert_path
end

# Configure Apache SSL
include_recipe 'apache2::mod_ssl'
web_app 'my-webapp' do
  cookbook 'ssl_certificate'
  # [...]
  ssl_key my_key_path
  ssl_cert my_cert_path
end

See templates documentation.

Nginx Example

Minimal nginx example using community nginx cookbook:

cert = ssl_certificate 'my-webapp' do
  notifies :restart, 'service[nginx]'
end

# Create a virtualhost for nginx
template ::File.join(node['nginx']['dir'], 'sites-available', 'my-webapp-ssl') do
  # You need to create a template for nginx to enable SSL support and read the
  # keys from ssl_key and ssl_chain_combined attributes.
  # You can use the *nginx.erb* partial template as shown below.
  source 'nginx_vhost.erb'
  mode 00644
  owner 'root'
  group 'root'
  variables(
    name: 'my-webapp-ssl',
    server_name: 'ssl.example.com',
    docroot: '/var/www',
    # [...]
    ssl_key: cert.key_path,
    ssl_cert: cert.chain_combined_path
  )
  notifies :reload, 'service[nginx]'
end

# Enable the virtualhost
nginx_site 'my-webapp-ssl' do
  enable true
end

# publish the certificate to an attribute, it may be useful
node.set['web-app']['ssl_cert']['content'] = cert.cert_content

Here's a nginx template example using the nginx.erb partial template:

<%# nginx_vhost.erb %>
server {
  server_name <%= @server_name %>;
  listen 443;
  # Path to the root of your installation
  root <%= @docroot %>;

  access_log <%= node['nginx']['log_dir'] %>/<%= @name %>-access.log combined;
  error_log  <%= node['nginx']['log_dir'] %>/<%= @name %>-error.log;

  index index.html;
  <%# [...] %>

  <%= render 'nginx.erb', cookbook: 'ssl_certificate' %>
}

See templates documentation.

Reading the Certificate from Attributes

The SSL certificate can be read from an attribute directly:

# Setting the attributes
node.default['mysite']['ssl_key']['content'] =
  '-----BEGIN PRIVATE KEY-----[...]'
node.default['mysite']['ssl_cert']['content'] =
  '-----BEGIN CERTIFICATE-----[...]'

# Creating the certificate
ssl_certificate 'mysite' do
  common_name 'cloud.mysite.com'
  namespace node['mysite']
  # this will read the node['mysite']['ssl_key']['content'] and
  # node['mysite']['ssl_cert']['content'] keys
  source 'attribute'
end

Alternative example using a namespace and node attributes:

# Setting the attributes
node.default['mysite']['common_name'] = 'cloud.mysite.com'
node.default['mysite']['ssl_key']['source'] = 'attribute'
node.default['mysite']['ssl_key']['content'] =
  '-----BEGIN PRIVATE KEY-----[...]'
node.default['mysite']['ssl_cert']['source'] = 'attribute'
node.default['mysite']['ssl_cert']['content'] =
  '-----BEGIN CERTIFICATE-----[...]'

# Creating the certificate
ssl_certificate 'mysite' do
  namespace node['mysite']
end

Reading the Certificate from a Data Bag

ssl_certificate 'mysite' do
  common_name 'cloud.mysite.com'
  source 'data-bag'
  bag 'ssl_data_bag'
  key_item 'key' # data bag item
  key_item_key 'content' # data bag item json key
  cert_item 'cert'
  cert_item_key 'content'
  encrypted true
  secret_file '/path/to/secret/file' # optional
end

Alternative example using a namespace and node attributes:

# Setting the attributes
node.default['mysite']['common_name'] = 'cloud.mysite.com'

node.default['mysite']['ssl_key']['source'] = 'data-bag'
node.default['mysite']['ssl_key']['bag'] = 'ssl_data_bag'
node.default['mysite']['ssl_key']['item'] = 'key'
node.default['mysite']['ssl_key']['item_key'] = 'content'
node.default['mysite']['ssl_key']['encrypted'] = true
node.default['mysite']['ssl_key']['secret_file'] = '/path/to/secret/file'

node.default['mysite']['ssl_cert']['source'] = 'data-bag'
node.default['mysite']['ssl_cert']['bag'] = 'ssl_data_bag'
node.default['mysite']['ssl_cert']['item'] = 'key'
node.default['mysite']['ssl_cert']['item_key'] = 'content'
node.default['mysite']['ssl_cert']['encrypted'] = true
node.default['mysite']['ssl_cert']['secret_file'] = '/path/to/secret/file'

# Creating the certificate
ssl_certificate 'mysite' do
  namespace node['mysite']
end

Reading the Certificate from Chef Vault

ssl_certificate 'mysite' do
  common_name 'cloud.mysite.com'
  source 'chef-vault'
  bag 'ssl_vault_bag'
  key_item 'key' # data bag item
  key_item_key 'content' # data bag item json key
  cert_item 'cert'
  cert_item_key 'content'
end

The same example, using a namespace and node attributes:

# Setting the attributes
node.default['mysite']['common_name'] = 'cloud.mysite.com'

node.default['mysite']['ssl_key']['source'] = 'chef-vault'
node.default['mysite']['ssl_key']['bag'] = 'ssl_vault_bag'
node.default['mysite']['ssl_key']['item'] = 'key'
node.default['mysite']['ssl_key']['item_key'] = 'content'

node.default['mysite']['ssl_cert']['source'] = 'chef-vault'
node.default['mysite']['ssl_cert']['bag'] = 'ssl_vault_bag'
node.default['mysite']['ssl_cert']['item'] = 'key'
node.default['mysite']['ssl_cert']['item_key'] = 'content'

# Creating the certificate
ssl_certificate 'mysite' do
  namespace node['mysite']
end

For testing you can enabled fall back to use unencrypted data bags if chef vault is not found by setting attribute ['chef-vault']['databag_fallback'] to true value

Reading the Certificate from Files

ssl_certificate 'mysite' do
  common_name 'cloud.mysite.com'
  source 'file'
  key_path '/path/to/ssl/key'
  cert_path '/path/to/ssl/cert'
end

The same example, using a namespace and node attributes:

# Setting the attributes
node.default['mysite']['common_name'] = 'cloud.mysite.com'

node.default['mysite']['ssl_key']['source'] = 'file'
node.default['mysite']['ssl_key']['path'] = '/path/to/ssl/key'

node.default['mysite']['ssl_cert']['source'] = 'file'
node.default['mysite']['ssl_cert']['path'] = '/path/to/ssl/cert'

# Creating the certificate
ssl_certificate 'mysite' do
  namespace node['mysite']
end

Reading the Certificate from Different Places

You can also read the certificate and the private key from different places each:

ssl_certificate 'mysite' do
  common_name 'cloud.mysite.com'

  # Read the private key from chef-vault
  key_source 'chef-vault'
  key_bag 'ssl_vault_bag'
  key_item 'key' # data bag item
  key_item_key 'content' # data bag item json key

  # Read the public cert from a non-encrypted data bag
  cert_source 'data-bag'
  cert_bag 'ssl_data_bag'
  cert_item 'cert'
  cert_item_key 'content'
  cert_encrypted false
end

The same example, using a namespace and node attributes:

# Setting the attributes
node.default['mysite']['common_name'] = 'cloud.mysite.com'

# Read the private key from chef-vault
node.default['mysite']['ssl_key']['source'] = 'chef-vault'
node.default['mysite']['ssl_key']['bag'] = 'ssl_vault_bag'
node.default['mysite']['ssl_key']['item'] = 'key'
node.default['mysite']['ssl_key']['item_key'] = 'content'

# Read the public cert from a non-encrypted data bag
node.default['mysite']['ssl_cert']['source'] = 'data-bag'
node.default['mysite']['ssl_cert']['bag'] = 'ssl_data_bag'
node.default['mysite']['ssl_cert']['item'] = 'key'
node.default['mysite']['ssl_cert']['item_key'] = 'content'
node.default['mysite']['ssl_cert']['encrypted'] = false

# Creating the certificate
ssl_certificate 'mysite' do
  namespace node['mysite']
end

Creating a Certificate with Subject Alternate Names

domain = 'mysite.com'
# SAN for mysite.com, foo.mysite.com, bar.mysite.com, www.mysite.com
node.default[domain]['ssl_cert']['subject_alternate_names'] =
  [domain, "foo.#{domain}", "bar.#{domain}", "www.#{domain}"]

ssl_certificate 'mysite.com' do
  namespace node[domain]
  key_source 'self-signed'
  cert_source 'self-signed'
end

The subject_alternate_names parameter adds DNS values by default. You can also include other kind of values using a colon to separate the type from the value:

domain = 'mysite.com'
node.default[domain]['email'] = '[email protected]'
node.default[domain]['ssl_cert']['subject_alternate_names'] =
  [
    'email:copy',
    "email:my@#{domain}",
    "URI:http://#{domain}/",
    'IP:192.168.7.1',
    'IP:13::17',
    'RID:1.2.3.4',
    'otherName:1.2.3.4;UTF8:some other identifier'
  ]

ssl_certificate 'mysite.com' do
  namespace node[domain]
  key_source 'self-signed'
  cert_source 'self-signed'
end

See the x509v3_config manual page for more information.

Reading Key, Certificate and Intermediary from a Data Bag

cert_name = 'chain-data-bag'
node.default[cert_name]['ssl_key']['source'] = 'data-bag'
node.default[cert_name]['ssl_key']['bag'] = 'ssl'
node.default[cert_name]['ssl_key']['item'] = 'key'
node.default[cert_name]['ssl_key']['item_key'] = 'content'
node.default[cert_name]['ssl_key']['encrypted'] = true
node.default[cert_name]['ssl_cert']['source'] = 'data-bag'
node.default[cert_name]['ssl_cert']['bag'] = 'ssl'
node.default[cert_name]['ssl_cert']['item'] = 'cert'
node.default[cert_name]['ssl_cert']['item_key'] = 'content'
node.default[cert_name]['ssl_chain']['name'] = 'chain-ca-bundle.pem'
node.default[cert_name]['ssl_chain']['source'] = 'data-bag'
node.default[cert_name]['ssl_chain']['bag'] = 'ssl'
node.default[cert_name]['ssl_chain']['item'] = 'chain'
node.default[cert_name]['ssl_chain']['item_key'] = 'content'

ssl_certificate 'chain-data-bag' do
  namespace cert_name
end

Creating a PKCS12 Containing Both the Certificate and the Private Key

ssl_certificate 'mysite' do
  common_name 'cloud.mysite.com'
  source 'self-signed'
  key_path '/etc/key/my.key'
  cert_path '/etc/cert/my.pem'
  pkcs12_path '/home/me/my.p12'
  pkcs12_passphrase 'I_Want_To_Secure_My_P12' # optional
end

Creating a Certificate from a Certificate Authority

ca_cert = '/usr/share/pki/ca-trust-source/anchors/CA.crt'
ca_key = '/usr/share/pki/ca-trust-source/anchors/CA.key'

cert = ssl_certificate 'test' do
  namespace node['test.com']
  key_source 'self-signed'
  cert_source 'with_ca'
  ca_cert_path ca_cert
  ca_key_path ca_key
  ca_key_passphrase ca_key_passphrase
end

Reading the CA Certificate from a Chef Vault Bag

In this example, we read the CA certificate from a Chef Vault and use it to generate the shelf-signed certificates:

# Create the CA from a Chef Vault bag

ca_cert = ssl_certificate 'ca.example.org' do
  common_name 'ca.example.org'
  source 'chef-vault'
  bag 'ssl'
  item 'ca_cert'
  key_item_key 'key_content'
  cert_item_key 'cert_content'
end

ssl_certificate 'example.org' do
  cert_source 'with_ca'
  ca_cert_path ca_cert.cert_path
  ca_key_path ca_cert.key_path
end

The vault bag content:

{
  "id": "ca_cert",
  "key_content": "-----BEGIN RSA PRIVATE KEY-----\nMIIE [...]",
  "cert_content": "-----BEGIN CERTIFICATE-----\nMIIE [...]"
}

The knife command to create the vault bag item:

$ knife vault create ssl ca_cert [...]

Managing Certificates Via Attributes

Sometimes you may want to use only node attributes to manage some of your SSL Certificates (instead of the ssl_certificate resource). You can do it using the ssl_certificate::attr_apply recipe and configuring them inside the node['ssl_certificate']['items'] array:

run_list(
  'recipe[ssl_certificate::attr_apply]'
)
override_attributes(
  'ssl_certificate' => {
    'items' => [
      {
        'name' => 'domain.com',
        'dir' => '/etc/nginx/ssl',
        'item' => 'domain_com',
        'source' => 'chef-vault',
        'bag' => 'ssl-vault',
        'key_item_key' => 'key',
        'cert_item_key' => 'cert',
        'chain_item_key' => 'chain',
        'chain_source' => 'chef-vault',
        'chain_bag' => 'ssl-vault',
        'chain_item' => 'domain_com',
        'chain_name' => 'domain.com.chain.pem'
      }
    ]
  }
)

Real-world Examples

Some cookbooks that use the ssl_certificate resource to implement SSL/TLS:

Attributes

Attribute Default Description
node['ssl_certificate']['user'] calculated Default SSL files owner user.
node['ssl_certificate']['group'] calculated Default SSL files owner group.
node['ssl_certificate']['key_dir'] calculated Default SSL key directory.
node['ssl_certificate']['cert_dir'] calculated Default SSL certificate directory.

Service Attributes

The following attributes are used to integrate SSL specific configurations with different services (Apache, nginx, ...). They are used internally by the apache and nginx templates.

Attribute Default Description
node['ssl_certificate']['service']['cipher_suite'] nil Service default SSL cipher suite.
node['ssl_certificate']['service']['protocols'] nil Service default SSL protocols.
node['ssl_certificate']['service']['apache'] calculated Apache web service httpd specific SSL attributes.
node['ssl_certificate']['service']['nginx'] calculated nginx web service specific SSL attributes.
node['ssl_certificate']['service']['compatibility'] nil Service SSL compatibility level (See below).
node['ssl_certificate']['service']['use_hsts'] true Whether to enable HSTS in the service.
node['ssl_certificate']['service']['use_stapling'] calculated Whether to enable OCSP stapling in the service (nginx only, use node['apache']['mod_ssl']['use_stapling'] for apache).
node['ssl_certificate']['service']['stapling_resolver'] calculated DNS resolver to use for OCSP. Only with Nginx.

See the ServiceHelpers class documentation to learn how to integrate them with new services.

Resources

ssl_certificate

Creates a SSL certificate.

By default the resource will create a self-signed certificate, but a custom one can also be used. The custom certificate can be read from several sources:

  • Attribute
  • Data Bag
  • Encrypted Data Bag
  • Chef Vault
  • File

ssl_certificate Actions

  • create: Creates the SSL certificate.

ssl_certificate Parameters

Parameter Default Description
namespace {} Node namespace to read the default values from, something like node['myapp']. See the documentation above for more information on how to use the namespace.
common_name namespace['common_name'] Server name or Common Name, used for self-signed certificates.
domain namespace['common_name'] common_name method alias.
country namespace['country'] Country, used for self-signed certificates.
city namespace['city'] City, used for self-signed certificates.
state namespace['state'] State or Province name, used for self-signed certificates.
organization namespace['city'] Organization or Company name, used for self-signed certificates.
department namespace['city'] Department or Organizational Unit, used for self-signed certificates.
email namespace['email'] Email address, used for self-signed certificates.
time 10 * 365 * 24 * 60 * 60 Attribute for setting self-signed certificate validity time in seconds or Time object instance.
years 10 Write only attribute for setting self-signed certificate validity period in years.
owner calculated Certificate files owner user.
group calculated Certificate files owner group.
dir nil Write only attribute for setting certificate path and key path (both) to a directory (key_dir and cert_dir).
source nil Write only attribute for setting certificate source and key source (both) to a value (key_source and cert_source). Can be 'self-signed', 'attribute', 'data-bag', 'chef-vault' or 'file'.
bag nil Write only attribute for setting certificate bag and key bag (both) to a value (key_bag and cert_bag).
item nil Write only attribute for setting certificate item name and key bag item name (both) to a value (key_item and cert_item).
encrypted nil Write only attribute for setting certificate encryption and key encryption (both) to a value (key_encrypted and cert_encrypted).
secret_file nil Write only attribute for setting certificate chef secret file and key chef secret file (both) to a value (key_secret_file and cert_secret_file).
key_path calculated Private key full path.
key_mode 0600 Private key file mode.
key_name "#{name}.key" Private key file name.
key_dir calculated Private key directory path.
key_source 'self-signed' Source type to get the SSL key from. Can be 'self-signed', 'attribute', 'data-bag', 'chef-vault' or 'file'.
key_bag namespace['ssl_key']['bag'] Name of the Data Bag where the SSL key is stored.
key_item namespace['ssl_key']['item'] Name of the Data Bag Item where the SSL key is stored.
key_item_key calculated Key of the Data Bag Item where the SSL key is stored.
key_encrypted false Whether the Data Bag where the SSL key is stored is encrypted.
key_length 2048 Integer that must be a power of 2, with a reasonable maximum of 4096. This defines the length of a generated RSA key.
key_secret_file nil Secret file used to decrypt the Data Bag where the SSL key is stored.
key_content calculated SSL key file content in clear. Be careful when using it.
cert_path calculated Public certificate full path.
cert_name "#{name}.pem" Public certiticate file name.
cert_dir calculated Public certificate directory path.
cert_source 'self-signed' Source type to get the SSL cert from. Can be 'self-signed', 'with_ca', 'attribute', 'data-bag', 'chef-vault' or 'file'.
cert_bag namespace['ssl_cert']['bag'] Name of the Data Bag where the SSL cert is stored.
cert_item calculated Name of the Data Bag Item where the SSL cert is stored.
cert_item_key calculated Key of the Data Bag Item where the SSL cert is stored.
cert_encrypted false Whether the Data Bag where the SSL cert is stored is encrypted.
cert_secret_file nil Secret file used to decrypt the Data Bag where the SSL cert is stored.
cert_content calculated SSL cert file content in clear.
subject_alternate_names nil Subject Alternate Names for the cert.
extended_key_usage nil Extended keyUsage flags array
chain_path calculated Intermediate certificate chain full path.
chain_name nil File name of intermediate certificate chain file. If this is not present, no chain file will be written.
chain_dir calculated Intermediate certificate chain directory path.
chain_source nil Source type to get the intermediate certificate chain from. Can be 'attribute', 'data-bag', 'chef-vault' or 'file'.
chain_bag calculated Name of the Data Bag where the intermediate certificate chain is stored.
chain_item calculated Name of the Data Bag Item where the intermediate certificate chain is stored.
chain_item_key calculated Key of the Data Bag Item where the intermediate certificate chain is stored.
chain_encrypted false Whether the Data Bag where the intermediate certificate chain is stored is encrypted.
chain_secret_file nil Secret file used to decrypt the Data Bag where the intermediate certificate chain is stored.
chain_content calculated Intermediate certificate chain file content in clear.
chain_combined_name calculated File name of intermediate certificate chain combined file (for nginx).
chain_combined_path calculated Intermediate certificate chain combined file full path (for nginx).
ca_cert_path nil Certificate Authority full path.
ca_key_path nil Key Authority full path.
ca_key_passphrase nil Key Authority passphrase.
pkcs12_path nil Optional PKCS12 full path.
pkcs12_passphrase nil Optional PKCS12 passphrase.

Templates

This cookbook includes a simple VirtualHost template which can be used by the web_app definition from the apache2 cookbook:

cert = ssl_certificate 'my-webapp' do
  namespace node['my-webapp']
  notifies :restart, 'service[apache2]'
end

include_recipe 'apache2'
include_recipe 'apache2::mod_ssl'
web_app 'my-webapp' do
  cookbook 'ssl_certificate'
  server_name cert.common_name
  docroot # [...]
  # [...]
  ssl_key cert.key_path
  ssl_cert cert.cert_path
  ssl_chain cert.chain_path
end

Partial Templates

This cookbook contains partial templates that you can include in your virtualhost templates to enable and configure the SSL protocol. These partial templates are available:

  • apache.erb: For Apache httpd web server.
  • nginx.erb: For nginx web server.

Partial Templates Parameters

Parameter Default Description
ssl_cert nil Public SSL certificate full path.
ssl_key nil Private SSL key full path.
ssl_chain nil Intermediate SSL certificate chain full path (apache only) (optional).
ssl_compatibility node attribute SSL compatibility level (See below).

Apache Partial Template

Using web_app Definition

If you are using the web_app definition, you should pass the @params variables to the partial template:

web_app 'my-webapp-ssl' do
  docroot node['apache']['docroot_dir']
  server_name cert.common_name
  # [...]
  ssl_key cert.key_path
  ssl_cert cert.cert_path
  ssl_chain cert.chain_path
end
<%# included by web_app definition %>
<VirtualHost *:443>
  ServerName <%= @params[:server_name] %>
  DocumentRoot <%= @params[:docroot] %>
  <%# [...] %>

  <%= render 'apache.erb', cookbook: 'ssl_certificate', variables: @params.merge(node: node) %>
</VirtualHost>

Using template Resource

cert = ssl_certificate 'my-webapp-ssl'
template ::File.join(node['apache']['dir'], 'sites-available', 'my-webapp-ssl') do
  source 'apache_vhost.erb'
  # [...]
  variables(
    # [...]
    ssl_key: cert.key_path,
    ssl_cert: cert.chain_combined_path,
    ssl_chain: cert.chain_path
  )
end

You can include the partial template as follows:

<%# included by template resource %>
<VirtualHost *:443>
  ServerName <%= @server_name %>
  DocumentRoot <%= @docroot %>
  <%# [...] %>

  <%= render 'apache.erb', cookbook: 'ssl_certificate' %>
</VirtualHost>

Nginx Partial Template

If you are using nginx template, we recommended to use the SslCertificate#chain_combined_path path value to set the ssl_cert variable instead of SslCertificate#cert_path. That's to ensure we always include the chained certificate if there is one. This will also work when there is no chained certificate.

cert = ssl_certificate 'my-webapp-ssl'
template ::File.join(node['nginx']['dir'], 'sites-available', 'my-webapp-ssl') do
  source 'nginx_vhost.erb'
  # [...]
  variables(
    # [...]
    ssl_key: cert.key_path,
    ssl_cert: cert.chain_combined_path
  )
end

See the examples above.

Securing Server Side TLS

You can change the SSL compatibility level based on the TLS recommendations in the Mozilla wiki using the ssl_compatibility template parameter:

cert = ssl_certificate 'my-webapp' do
  namespace node['my-webapp']
  notifies :restart, 'service[apache2]'
end

include_recipe 'apache2'
include_recipe 'apache2::mod_ssl'
web_app 'my-webapp' do
  cookbook 'ssl_certificate'
  server_name cert.common_name
  docroot # [...]
  # [...]
  ssl_key cert.key_path
  ssl_cert cert.cert_path
  ssl_chain cert.chain_path
  ssl_compatibility :modern # :modern | :intermediate | :old
end

You can also use the node['ssl_certificate']['service']['compatibility'] node attribute to change the compatibility level used by default.

Testing

See TESTING.md.

ChefSpec Matchers

ssl_certificate(name)

Helper method for locating a ssl_certificate resource in the collection.

resource = chef_run.ssl_certificate('postfixadmin')
expect(resource).to notify('service[apache2]').to(:restart)

create_ssl_certificate(name)

Assert that the Chef run creates ssl_certificate.

expect(chef_run).to create_ssl_certificate('cloud.mysite.com')

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: Raul Rodriguez ([email protected])
Author: Xabier de Zuazo ([email protected])
Contributor: Steve Meinel
Contributor: Djuri Baars
Contributor: Elliott Davis
Contributor: Jeremy MAURO
Contributor: Benjamin Nรธrgaard
Contributor: Stanislav Bogatyrev
Contributor: Karl Svec
Contributor: Nikita Borzykh
Contributor: Baptiste Courtois
Contributor: Taliesin Sisson
Contributor: Alexey Demidov
Contributor: Ali Ardestani
Contributor: HawkAndBaby
Contributor: Andrew J. Brown
Copyright: Copyright (c) 2015-2017, 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.

ssl_certificate-cookbook's People

Contributors

alexeydemidov avatar alisade avatar annih avatar bitphage avatar dsbaars avatar karlsvec avatar realloc avatar smeinel avatar zuazo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ssl_certificate-cookbook's Issues

SANs should support IPs

When creating our certs they need to also contain a SAN with the ip of the host. Right now this does not occur because the provider only supports DNS entries.
It should look like the following:

[ alt_names ]
#
IP.1 = 192.168.1.100
IP.2 = 127.0.0.1

Changes to certificate files do not trigger notifications

Whenever I make a change to the certificate or private key, the files are updated as expected, but notifications in the ssl_certificate resource are not triggered. For example, if I change the certificate for an nginx instance, I want to notify nginx to restart.

ssl_certificate resource fires notifications when resource is not executed

ssl_certificate 2.1.0 running on Chef 12.21.20.

Minimum verifiable example:

node.default['example.com']['ssl_cert']['source'] = 'self-signed'
node.default['example.com']['ssl_key']['source'] = 'self-signed'

ssl_certificate 'example.com' do
  common_name 'example.com'
  namespace node['example.com']
  notifies :run, 'ruby_block[foo]', :delayed
  not_if { true }
end

ruby_block 'foo' do
  block { nil }
  action :nothing
end

In theory, the ruby_block should never execute. It has action :nothing, and although it's triggered via notification from ssl_certificate, ssl_certificate has not_if { true }. However, we can see from a chef-client run that ssl_certificate does not run (skipped due to not_if) but the notification is fired off anyway and the ruby_block is executed:

       Compiling Cookbooks...
       [2018-02-21T12:00:22-07:00] INFO: HTTP Request Returned 404 Not Found: Object not found: 
       Converging 2 resources
       Recipe: ssl_certificate_test::default
         * ssl_certificate[example.com] action create[2018-02-21T12:00:22-07:00] INFO: Processing ssl_certificate[example.com] action create (ssl_certificate_test::default line 4)
        (skipped due to not_if)
         * ruby_block[foo] action nothing[2018-02-21T12:00:22-07:00] INFO: Processing ruby_block[foo] action nothing (ssl_certificate_test::default line 11)
        (skipped due to action :nothing)
       [2018-02-21T12:00:22-07:00] INFO: ssl_certificate[example.com] sending run action to ruby_block[foo] (delayed)
         * ruby_block[foo] action run[2018-02-21T12:00:22-07:00] INFO: Processing ruby_block[foo] action run (ssl_certificate_test::default line 11)
       [2018-02-21T12:00:22-07:00] INFO: ruby_block[foo] called
       
           - execute the ruby block foo
       [2018-02-21T12:00:22-07:00] INFO: Chef Run complete in 21.660891891 seconds
       
       Running handlers:
       [2018-02-21T12:00:23-07:00] INFO: Running report handlers
       Running handlers complete
       [2018-02-21T12:00:23-07:00] INFO: Report handlers complete
       Chef Client finished, 1/3 resources updated in 23 seconds

Using action :nothing instead of not_if { true } displays similar erroneous behavior:

       Compiling Cookbooks...
       [2018-02-21T12:08:05-07:00] INFO: HTTP Request Returned 404 Not Found: Object not found: 
       Converging 2 resources
       Recipe: ssl_certificate_test::default
         * ssl_certificate[example.com] action nothing[2018-02-21T12:08:05-07:00] INFO: Processing ssl_certificate[example.com] action nothing (ssl_certificate_test::default line 4)
        (skipped due to action :nothing)
         * ruby_block[foo] action nothing[2018-02-21T12:08:05-07:00] INFO: Processing ruby_block[foo] action nothing (ssl_certificate_test::default line 11)
        (skipped due to action :nothing)
       [2018-02-21T12:08:05-07:00] INFO: ssl_certificate[example.com] sending run action to ruby_block[foo] (delayed)
         * ruby_block[foo] action run[2018-02-21T12:08:05-07:00] INFO: Processing ruby_block[foo] action run (ssl_certificate_test::default line 11)
       [2018-02-21T12:08:05-07:00] INFO: ruby_block[foo] called
       
           - execute the ruby block foo
       [2018-02-21T12:08:05-07:00] INFO: Chef Run complete in 1.59575882 seconds
       
       Running handlers:
       [2018-02-21T12:08:05-07:00] INFO: Running report handlers
       Running handlers complete
       [2018-02-21T12:08:05-07:00] INFO: Report handlers complete
       Chef Client finished, 1/3 resources updated in 03 seconds
       Finished converging <default-cub-rh7> (0m6.62s).

Compare this to the following similar recipe that uses a second ruby_block instead of ssl_certificate, where the notification is well-behaved, and no notification is fired and neither resource executes:

ruby_block 'bar' do
  block { nil }
  notifies :run, 'ruby_block[foo]', :delayed
  not_if { true }
end

ruby_block 'foo' do
  block { nil }
  action :nothing
end
       Compiling Cookbooks...
       [2018-02-21T12:05:18-07:00] INFO: HTTP Request Returned 404 Not Found: Object not found: 
       Converging 2 resources
       Recipe: ssl_certificate_test::default
         * ruby_block[bar] action run[2018-02-21T12:05:18-07:00] INFO: Processing ruby_block[bar] action run (ssl_certificate_test::default line 1)
        (skipped due to not_if)
         * ruby_block[foo] action nothing[2018-02-21T12:05:18-07:00] INFO: Processing ruby_block[foo] action nothing (ssl_certificate_test::default line 7)
        (skipped due to action :nothing)
       [2018-02-21T12:05:18-07:00] INFO: Chef Run complete in 21.50287029 seconds
       
       Running handlers:
       [2018-02-21T12:05:18-07:00] INFO: Running report handlers
       Running handlers complete
       [2018-02-21T12:05:18-07:00] INFO: Report handlers complete
       Chef Client finished, 0/2 resources updated in 23 seconds
       Finished converging <default-cub-rh7> (0m26.52s).

Compilation fails when using Chef Client 16.1.16

Cookbook Version

2.1.0

Chef Client Version

16.1.16

Platform Details

Centos 7.8

Scenario

Recipe compilation

Steps to Reproduce

Add the ssl_certificate dependency, add the snippet below in a recipe, and then converge it.

cert = ssl_certificate 'webapp1' do
  namespace node['webapp1'] # optional but recommended
end

Expected Result

Compiles successfully

Actual Result

       ================================================================================
       Recipe Compile Error in /tmp/kitchen/cache/cookbooks/parent-cookbook/recipes/default.rb
       ================================================================================
       
       NoMethodError
       -------------
       undefined method `ssl_certificate' for cookbook: child-cookbook, recipe: default :Chef::Recipe
       
       Cookbook Trace:
       ---------------
         /tmp/kitchen/cache/cookbooks/child-cookbook/recipes/default.rb:9:in `from_file'
         /tmp/kitchen/cache/cookbooks/parent-cookbook/recipes/default.rb:12:in `from_file'
       
       Relevant File Content:
       ----------------------
       /tmp/kitchen/cache/cookbooks/child-cookbook/recipes/default.rb:
       
         2:  # Cookbook Name:: child-cookbook
         3:  # Recipe:: default
         4:  #
         5:  # Copyright (c) 2016 The Authors, All Rights Reserved.
         6:  # include_recipe '****-sshd::default'
         7:  #
         8:  
         9>> cert = ssl_certificate 'proftpd' do
        10:    common_name node['child-cookbook']['public-name']
        11:  end
       
       System Info:
       ------------
       chef_version=16.1.16
       platform=centos
       platform_version=7.8.2003
       ruby=ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
       program_name=/opt/chef/bin/chef-solo
       executable=/opt/chef/bin/chef-solo
       
       
       Running handlers:
       [2020-06-10T18:19:53+00:00] ERROR: Running exception handlers
       Running handlers complete
       [2020-06-10T18:19:53+00:00] ERROR: Exception handlers complete
       Chef Infra Client failed. 0 resources updated in 10 seconds
       [2020-06-10T18:19:53+00:00] FATAL: Stacktrace dumped to /tmp/kitchen/cache/chef-stacktrace.out
       [2020-06-10T18:19:53+00:00] FATAL: Please provide the contents of the stacktrace.out file if you file a bug report
       [2020-06-10T18:19:53+00:00] FATAL: NoMethodError: undefined method `ssl_certificate' for cookbook: child-cookbook, recipe: default :Chef::Recipe
>>>>>> ------Exception-------
>>>>>> Class: Kitchen::ActionFailed
>>>>>> Message: 1 actions failed.
>>>>>>     Converge failed on instance <default-centos-78>.  Please see .kitchen/logs/default-centos-78.log for more details
>>>>>> ----------------------
>>>>>> Please see .kitchen/logs/kitchen.log for more details
>>>>>> Also try running `kitchen diagnose --all` for configuration

Failure to create combined chain but not the chain

Cookbook Version

2.1.0

Chef Client Version

13.8.5

Scenario

I want to create a certificate, its key and a combined chain from an encrypted data bag, but not the chain.

Steps to Reproduce

ssl_certificate 'vault-server' do
  source 'data-bag'
  bag 'a-data-bag'
  item 'a-data-bag-item'

  chain_name nil
  chain_combined_name 'vault-server.bundle.pem'
  
  cert_item_key 'vault-server-cert'
  key_item_key 'vault-server-key'
  chain_item_key 'vault-server-chain'
  
  encrypted true
  secret_file Chef::Config[:encrypted_data_bag_secret]
  
  key_mode '0640'
end

The data bag item contains all the item keys and the key vault-server-chain contains the certificates of the CAs (intermediate and root).

Expected Result

The file vault-server.bundle.pem should have as content the certificate followed by the chain.

Actual Result

The file vault-server.bundle.pem only contains just the certificate.
However if I set chain_name (eg: to vault-server.chain.pem) then the combined chain is correctly created.

This issue is very similar to #38 the difference is that the chain_combined is expecting the chain file to exist.

Do not assume ca_key_path is a RSA key

Cookbook Version

2.1.0

Scenario

Currently the cookbook assumes ca_key_path is a RSA key, which isn't always true.
https://github.com/zuazo/ssl_certificate-cookbook/blob/master/libraries/resource_ssl_certificate_generators.rb#L168

def generate_ca_from_content(cert_content, key_content)
  ca_cert = OpenSSL::X509::Certificate.new(cert_content)
  ca_key = OpenSSL::PKey::RSA.new(key_content, ca_key_passphrase)
  [ca_cert, ca_key]
end

This could be easily fixed with:

  ca_key = OpenSSL::PKey.read(key_content, ca_key_passphrase)

https://ruby.github.io/openssl/OpenSSL/PKey.html

Extended config file

For docker client auth, the certificates need to have the following attribute:

extendedKeyUsage = clientAuth

I don't see a way to add that to a certificate currently

chain_name defaults to nil but needs to be defined to use chain

In following example (chain_name omitted) chain content is ignored and not included in chained site.com.pem.chained.pem. While chain_name default value is documented in README but it probably should specify that you need to explicitly define chain_name to use intermediate certificate.

cert = ssl_certificate "site.com" do
    source 'data-bag'
    bag 'ssl_certs'
    key_item "site.com"
    key_item_key 'key'
    cert_item "site.com"
    cert_item_key 'cert'
    chain_item "site.com"
    chain_item_key 'chain'
  end

New release?

I am looking to make use of the data bag fallback attribute, are there plans to make a new release soon?

Chain is always created when it shoudn't

Cookbook Version

2.1.0

Chef Client Version

13.8.5

Scenario

I want to create a certificate and its key from a data bag but not its chain.

Steps to Reproduce

base_ssl_path = '/etc/consul/ssl'
ssl_certificate 'consul-server' do
  source 'data-bag'
  bag 'a-test-bag'
  item 'a-test-bag-item'
  
  key_item_key 'key'
  cert_item_key 'cert'
  
  encrypted true
  secret_file Chef::Config[:encrypted_data_bag_secret]
  
  cert_dir base_ssl_path
  key_dir base_ssl_path
  key_mode '0640'
  chain_name nil
end

Expected Result

No chain file should be created.

Actual Result

A file named consul-server.pem.chained.pem is created at /etc/consul/ssl.

If I also set:

  chain_source nil
  chain_content nil

The resource tries to read from a data bag and fails with:
Cannot read SSL intermediary chain from data bag: a-test-bag.a-test-bag-item[]

Creating a PEM file containing both cert and private key?

Is there a way to use the ssl_certificate cookbook to create a PEM file containing the signed certificate, intermediate certs (if any), and private key, in that order? I'm trying to write a cookbook to configure some HAProxy instances, and HAProxy needs the certs and key to live in a PEM file together. We already use ssl_certificate to manage SSL certs for other applications and generate self-signed certs in TestKitchen, so I was hoping to use it for HAProxy too.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.