diff --git a/data/common.yaml b/data/common.yaml new file mode 100644 index 0000000..772d115 --- /dev/null +++ b/data/common.yaml @@ -0,0 +1,15 @@ +--- +hitch::package_name: "hitch" +hitch::service_name: "hitch" +hitch::config_root: "/etc/hitch" +hitch::config_file: "/etc/hitch/hitch.conf" +hitch::dhparams_file: "/etc/hitch/dhparams.pem" +hitch::dhparams_content: undef +hitch::purge_config_root: false +hitch::file_owner: "root" +hitch::frontend: "[*]:443" +hitch::backend: "[::1]:80" +hitch::write_proxy_v2: "off" +hitch::ciphers: "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH" +hitch::prefer_server_ciphers: "on" +hitch::domains: {} diff --git a/data/os/Debian.yaml b/data/os/Debian.yaml new file mode 100644 index 0000000..aaf2796 --- /dev/null +++ b/data/os/Debian.yaml @@ -0,0 +1,3 @@ +--- +hitch::user: "_hitch" +hitch::group: "_hitch" diff --git a/data/os/RedHat.yaml b/data/os/RedHat.yaml new file mode 100644 index 0000000..df16fcf --- /dev/null +++ b/data/os/RedHat.yaml @@ -0,0 +1,3 @@ +--- +hitch::user: "hitch" +hitch::group: "hitch" diff --git a/hiera.yaml b/hiera.yaml new file mode 100644 index 0000000..8ae0729 --- /dev/null +++ b/hiera.yaml @@ -0,0 +1,12 @@ +--- +version: 5 +defaults: + datadir: data + data_hash: yaml_data + +hierarchy: + - name: "OS Family" + path: "os/%{facts.os.family}.yaml" + + - name: "common" + path: "common.yaml" diff --git a/manifests/config.pp b/manifests/config.pp index 6eecaf3..28a6ab4 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -1,58 +1,63 @@ # == Class hitch::config # # This class is called from hitch for service config. # -class hitch::config { +# @api private +class hitch::config ( + Stdlib::Absolutepath $config_root, + Stdlib::Absolutepath $config_file, + Stdlib::Absolutepath $dhparams_file, + Boolean $purge_config_root, + String $file_owner, + String $user, + String $group, + String $dhparams_content, + String $write_proxy_v2, + String $frontend, + String $backend, + String $ciphers, +) { - validate_absolute_path($::hitch::config_root) - validate_absolute_path($::hitch::config_file) - validate_absolute_path($::hitch::dhparams_file) - - if $::hitch::dhparams_content { - validate_re($::hitch::dhparams_content, 'BEGIN DH PARAMETERS') - } - - file { $::hitch::config_root: + file { $config_root: ensure => directory, recurse => true, - purge => $::hitch::purge_config_root, - owner => $::hitch::file_owner, - group => $::hitch::group, + purge => $purge_config_root, + owner => $file_owner, + group => $group, mode => '0750', } - concat { $::hitch::config_file: + concat { $config_file: ensure => present, } - if $::hitch::dhparams_content { - file { $::hitch::dhparams_file: + if $dhparams_content { + file { $dhparams_file: ensure => present, - owner => $::hitch::file_owner, - group => $::hitch::group, + owner => $file_owner, + group => $group, mode => '0640', - content => $::hitch::dhparams_content, + content => $dhparams_content, } } else { exec { "${title} generate dhparams": path => '/usr/local/bin:/usr/bin:/bin', - command => "openssl dhparam 2048 -out ${::hitch::dhparams_file}", - creates => $::hitch::dhparams_file, + command => "openssl dhparam 2048 -out ${dhparams_file}", + creates => $dhparams_file, } - - -> file { $::hitch::dhparams_file: + + -> file { $dhparams_file: ensure => present, - owner => $::hitch::file_owner, - group => $::hitch::group, + owner => $file_owner, + group => $group, mode => '0640', } } concat::fragment { "${title} config": content => template('hitch/hitch.conf.erb'), - target => $::hitch::config_file, + target => $config_file, } - create_resources('hitch::domain', $::hitch::domains) } diff --git a/manifests/domain.pp b/manifests/domain.pp index c472a3b..1c8da28 100644 --- a/manifests/domain.pp +++ b/manifests/domain.pp @@ -1,129 +1,116 @@ # == Define hitch::domain # # This define installs pem files to the config root, and configures -# them in the hitch config file +# them in the hitch config file. +# +# The CA certificate (if present), server certificate, key and DH +# parameters are concatenated, and placed in the hitch configuration. +# +# You can specify cacert, cert and key with either _content or _source +# suffix. +# +# Parameters: +# +# @param ensure [Enum['present','absent']] +# The desired state of the hitch domain. Default is 'present'. +# +# @param default [Boolean] +# If there are multiple domains, set this to true to make this the +# default domain used by hitch. If there is only one domain, it +# will be the default domain no matter what you set here. Defaults +# to false. +# +# @param [Optional[String]] cacert_content +# A PEM encoded CA certificate. +# +# @param [Optional[Stdlib::Filesource]] cacert_source +# Path to a PEM encoded CA certificate. +# +# @param [Optional[String]] cert_content +# A PEM encoded certificate. This must be a certificate matching the +# key. +# +# @param [Optional[Stdlib::Filesource]] cert_source +# Path to a PEM encoded certificate. This must be a certificate +# matching the key. +# +# @param [Optional[String]] key_content +# A PEM encoded key. This must be a key matching the certificate. +# +# @param [Optional[Stdlib::Filesource]] key_source +# Path to a PEM encoded key. This must be a key matching the +# certificate. # define hitch::domain ( - $ensure = present, - $cacert_content = undef, - $cacert_source = undef, - $cert_content = undef, - $cert_source = undef, - $dhparams_content = undef, - $dhparams_source = undef, - $key_content = undef, - $key_source = undef, + Enum['present', 'absent'] $ensure = present, + Boolean $default = false, + Optional[String] $cert_content = undef, + Optional[String] $key_content = undef, + Optional[String] $cacert_content = undef, + Optional[Stdlib::Filesource] $cert_source = undef, + Optional[Stdlib::Filesource] $key_source = undef, + Optional[Stdlib::Filesource] $cacert_source = undef, + ) { - # Parameter validation - - validate_re($ensure, ['^present$', '^absent$']) - - # Exactly one of $key_source and $key_content - if ($key_content and $key_source) or (! $key_content and ! $key_source) { - fail("Hitch::Domain[${title}]: Please provide key_source or key_domain") - } - if $key_content { - validate_re($key_content, 'PRIVATE KEY') - $_key_content="${key_content}\n" - } - - # Exactly one of $cert_content and $cert_source - if ($cert_content and $cert_source) or (!$cert_content and !$cert_source) { - fail("Hitch::Domain[${title}]: Please provide cert_source or cert_domain") - } - if $cert_content { - validate_re($cert_content, 'CERTIFICATE') - $_cert_content="${cert_content}\n" - } - - # One or zero of $cacert_content or $cacert_source - if ($cacert_content and $cacert_source) { - fail("Hitch::Domain[${title}]: Please do not specify both cacert_source and cacert_domain") - } - if $cacert_content { - validate_re($cacert_content, 'CERTIFICATE') - $_cacert_content="${cacert_content}\n" + if ($key_content and $cert_content) { + validate_x509_rsa_key_pair($cert_content, $key_content) } - # One of $dhparams_content or $dhparams_source, with fallback to - # $::hitch::dhparams_file - if ($dhparams_content and $dhparams_source) { - fail("Hitch::Domain[${title}]: Please do not specify both dhparams_source and dhparams_domain") - } - if $dhparams_content { - validate_re($dhparams_content, 'DH PARAMETERS') - $_dhparams_content="${dhparams_content}\n" - } - - include ::hitch - include ::hitch::config - - $config_file = $::hitch::config_file - validate_absolute_path($config_file) - $pem_file="${::hitch::config_root}/${title}.pem" - validate_absolute_path($pem_file) + $config_root = $hitch::config_root + $config_file = $hitch::config_file + $dhparams_file = $hitch::dhparams_file + $pem_file="${config_root}/${title}.pem" + Concat::Fragment { + notify => Class['hitch::service'], + } # Add a line to the hitch config file concat::fragment { "hitch::domain ${title}": target => $config_file, content => "pem-file = \"${pem_file}\"\n", - notify => Class['hitch::service'], } # Create the pem file, with (optional) ca certificate chain, a # certificate, a key, and finally the dh parameters concat { $pem_file: ensure => $ensure, mode => '0640', owner => $::hitch::file_owner, group => $::hitch::group, notify => Class['hitch::service'], } concat::fragment {"${title} key": - content => $_key_content, - source => $key_source, target => $pem_file, order => '01', + content => $key_content, + source => $key_source, } concat::fragment {"${title} cert": - content => $_cert_content, - source => $cert_source, target => $pem_file, order => '02', + content => $cert_content, + source => $cert_source, } if ($cacert_content or $cacert_source) { concat::fragment {"${title} cacert": - content => $_cacert_content, - source => $cacert_source, target => $pem_file, order => '03', + content => $cacert_content, + source => $cacert_source, } } - if ! $dhparams_content { - if $dhparams_source { - $_dhparams_source = $dhparams_source - } - else { - $_dhparams_source = $::hitch::dhparams_file - File[$::hitch::dhparams_file] -> Concat::Fragment["${title} dhparams"] - } - } - - if ($dhparams_content or $_dhparams_source) { - concat::fragment {"${title} dhparams": - content => $_dhparams_content, - source => $_dhparams_source, - target => $pem_file, - order => '04', - } + concat::fragment {"${title} dhparams": + target => $pem_file, + source => $dhparams_file, + order => '04', } } diff --git a/manifests/init.pp b/manifests/init.pp index a418d47..a617a87 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,42 +1,87 @@ # Class: hitch # =========================== # # Full description of class hitch here. # # Parameters # ---------- # -# * `package_name` -# Package name for installing hitch +# @param package_name [String] +# Package name for installing hitch. # -# * `service_name` -# Service name for the hitch service +# @param service_name [String] +# Service name for the hitch service. # -# * `config_file` -# Configuration file. Default: /etc/hitch/hitch.conf +# @param user [String] +# User running the service. # -# * `config_root` +# @param group [String] +# Group running the service. +# +# @param file_owner [String] +# User owning the configuration files. Defaults to "root". +# +# @param dhparams_file [Stdlib::Absolutepath] +# Path to file for Diffie-Hellman parameters, which are shared +# by all domains. +# +# @param dhparams_content [Optional[String]] +# Content for the DH parameter file. If unset, DH parameters will +# be generated on the node, which may take a long time. +# +# @param config_root [Stdlib::Absolutepath] # Configuration root directory. Default: /etc/hitch/ -class hitch ( - $package_name = $::hitch::params::package_name, - $service_name = $::hitch::params::service_name, - $file_owner = $::hitch::params::file_owner, - $config_file = $::hitch::params::config_file, - $dhparams_file = $::hitch::params::dhparams_file, - $dhparams_content = $::hitch::params::dhparams, - $config_root = $::hitch::params::config_root, - $purge_config_root = $::hitch::params::purge_config_root, - $frontend = $::hitch::params::frontend, - $backend = $::hitch::params::backend, - $write_proxy_v2 = $::hitch::params::write_proxy_v2, - $ciphers = $::hitch::params::ciphers, - $domains = $::hitch::params::domains, -) inherits ::hitch::params { +# +# @param purge_config_root [Boolean] +# If true, will delete all unmanaged files from the config_root. +# Defaults to false. +# +# @param frontend[String] +# The listening frontend for hitch. - # validate parameters here +class hitch ( + String $package_name, + String $service_name, + String $user, + String $group, + String $file_owner, + Stdlib::Absolutepath $config_file, + Stdlib::Absolutepath $dhparams_file, + Stdlib::Absolutepath $config_root, + Boolean $purge_config_root, + String $frontend, + String $backend, + String $write_proxy_v2, + String $ciphers, + Optional[Hash] $domains, + Optional[String] $dhparams_content = undef, +) { - class { '::hitch::install': } - -> class { '::hitch::config': } - ~> class { '::hitch::service': } + class { '::hitch::install': + package => $package_name + } + -> class { '::hitch::config': + config_root => $config_root, + config_file => $config_file, + dhparams_file => $dhparams_file, + dhparams_content => $dhparams_content, + purge_config_root => $purge_config_root, + file_owner => $file_owner, + user => $user, + group => $group, + frontend => $frontend, + backend => $backend, + write_proxy_v2 => $write_proxy_v2, + ciphers => $ciphers, + } + ~> class { '::hitch::service': + service_name => $service_name, + } -> Class['::hitch'] + + $domains.each |$domain_title, $domain_params| { + hitch::domain { $domain_title: + * => $domain_params + } + } } diff --git a/manifests/install.pp b/manifests/install.pp index 19b406e..fb076f5 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -1,15 +1,17 @@ # == Class hitch::install # # This class is called from hitch for install. # -class hitch::install { +class hitch::install ( + String $package, +) { if $::osfamily == 'RedHat' { - ensure_packages(['epel-release']) - Package['epel-release'] -> Package[$::hitch::package_name] + ensure_resource('package', 'epel-release', { 'ensure' => 'present' }) + Package['epel-release'] -> Package[$package] } - package { $::hitch::package_name: + package { $package: ensure => present, } } diff --git a/manifests/params.pp b/manifests/params.pp deleted file mode 100644 index 193e73f..0000000 --- a/manifests/params.pp +++ /dev/null @@ -1,39 +0,0 @@ -# == Class hitch::params -# -# This class is meant to be called from hitch. -# It sets variables according to platform. -# -class hitch::params { - - $config_root = '/etc/hitch' - $config_file = '/etc/hitch/hitch.conf' - $dhparams_file = '/etc/hitch/dhparams.pem' - $dhparams_content = undef - $purge_config_root = false - $file_owner = 'root' - - $frontend = '[*]:443' - $backend = '[::1]:80' - $write_proxy_v2 = 'off' - $ciphers = 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH' - $prefer_server_ciphers = 'on' - $domains = {} - - case $::osfamily { - 'Debian': { - $package_name = 'hitch' - $service_name = 'hitch' - $user = '_hitch' - $group = '_hitch' - } - 'RedHat', 'Amazon': { - $package_name = 'hitch' - $service_name = 'hitch' - $user = 'hitch' - $group = 'hitch' - } - default: { - fail("${::operatingsystem} not supported") - } - } -} diff --git a/manifests/service.pp b/manifests/service.pp index ef3383a..32f9576 100644 --- a/manifests/service.pp +++ b/manifests/service.pp @@ -1,14 +1,16 @@ # == Class hitch::service # # This class is meant to be called from hitch. # It ensure the service is running. # -class hitch::service { +class hitch::service ( + String $service_name, +) { - service { $::hitch::service_name: + service { $service_name: ensure => running, enable => true, hasstatus => true, hasrestart => true, } } diff --git a/spec/classes/hitch_spec.rb b/spec/classes/hitch_spec.rb index 4a7c2f7..7aa5fa8 100644 --- a/spec/classes/hitch_spec.rb +++ b/spec/classes/hitch_spec.rb @@ -1,71 +1,11 @@ require 'spec_helper' describe 'hitch' do - context 'supported operating systems' do - on_supported_os.each do |os, facts| - context "on #{os}" do - let(:facts) do - facts - end + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } - context 'hitch class without any parameters' do - it { is_expected.to compile.with_all_deps } - - it { is_expected.to contain_class('hitch') } - it { is_expected.to contain_class('hitch::params') } - it { is_expected.to contain_class('hitch::install').that_comes_before('hitch::config') } - it { is_expected.to contain_class('hitch::config') } - it { is_expected.to contain_class('hitch::service').that_subscribes_to('hitch::config') } - - it { is_expected.to contain_service('hitch') } - it { is_expected.to contain_package('hitch').with_ensure('present') } - - it { is_expected.to contain_file('/etc/hitch') } - it { is_expected.to contain_file('/etc/hitch/dhparams.pem') } - it { is_expected.to contain_concat('/etc/hitch/hitch.conf') } - it { is_expected.to contain_concat__fragment('hitch::config config') } - it { is_expected.to contain_exec('hitch::config generate dhparams') } - - context 'osfamily specifics' do - if facts[:osfamily] == 'RedHat' - it { is_expected.to contain_package('epel-release') } - else - it { is_expected.not_to contain_package('epel-release') } - end - end - end - - context 'hitch class with domains' do - let(:params) do - { domains: { - 'example.com' => { - 'key_content' => '-----BEGIN PRIVATE KEY-----', - 'cert_content' => '-----BEGIN CERTIFICATE-----', - 'cacert_content' => '-----BEGIN CERTIFICATE-----', - 'dhparams_content' => '-----BEGIN DH PARAMETERS-----', - }, - } } - end - - it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_hitch__domain('example.com') } - it { is_expected.to contain_file('/etc/hitch/example.com.pem') } - it { is_expected.to contain_concat__fragment('hitch::domain example.com') } - end - end - end - end - - context 'unsupported operating system' do - describe 'hitch class without any parameters on Solaris/Nexenta' do - let(:facts) do - { - osfamily: 'Solaris', - operatingsystem: 'Nexenta', - } - end - - it { expect { is_expected.to contain_package('hitch') }.to raise_error(Puppet::Error, %r{Nexenta not supported}) } + it { is_expected.to compile } end end end diff --git a/spec/defines/hitch__domain_spec.rb b/spec/defines/hitch__domain_spec.rb index 294c969..5e6c19a 100644 --- a/spec/defines/hitch__domain_spec.rb +++ b/spec/defines/hitch__domain_spec.rb @@ -1,87 +1,62 @@ require 'spec_helper' -describe 'hitch::domain' do - context 'supported operating systems' do - on_supported_os.each do |os, facts| - context "on #{os}" do - let(:facts) do - facts - end - let(:title) { 'example.com' } - - context 'with all content parameters' do - let(:params) do - { - 'cacert_content' => '-----BEGIN CERTIFICATE-----', - 'cert_content' => '-----BEGIN CERTIFICATE-----', - 'dhparams_content' => '-----BEGIN DH PARAMETERS-----', - 'key_content' => '-----BEGIN PRIVATE KEY-----', - } - end - - it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_hitch__domain('example.com') } - - # for the pem file - it { is_expected.to contain_concat('/etc/hitch/example.com.pem') } - it { is_expected.to contain_concat__fragment('example.com cacert') } - it { is_expected.to contain_concat__fragment('example.com cert') } - it { is_expected.to contain_concat__fragment('example.com key') } - it { is_expected.to contain_concat__fragment('example.com dhparams') } +tls_data = gen_test_tls_data +key = tls_data[0].to_s +cert = tls_data[1].to_s - # for the config file - it { is_expected.to contain_concat('/etc/hitch/hitch.conf') } - it { is_expected.to contain_concat__fragment('hitch::domain example.com') } +describe 'hitch::domain' do + let(:title) { 'example.com' } + + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + context 'with source parameters' do + let(:params) do + { + 'key_source' => '/path/to/key', + 'cert_source' => '/path/to/certificate', + 'cacert_source' => '/path/to/cacertificate', + } end - context 'with all source parameters' do - let(:params) do - { - 'cacert_source' => '/tmp/cacert.pem', - 'cert_source' => '/tmp/cert.pem', - 'dhparams_source' => '/tmp/dhparams.pem', - 'key_source' => '/tmp/key.pem', - } - end + it { is_expected.to compile } + it { is_expected.to contain_hitch__domain('example.com') } - it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_hitch__domain('example.com') } + # for the pem file + it { is_expected.to contain_concat('/etc/hitch/example.com.pem') } + it { is_expected.to contain_concat__fragment('example.com cacert') } + it { is_expected.to contain_concat__fragment('example.com cert') } + it { is_expected.to contain_concat__fragment('example.com key') } + it { is_expected.to contain_concat__fragment('example.com dhparams') } - # for the pem file - it { is_expected.to contain_concat('/etc/hitch/example.com.pem') } - it { is_expected.to contain_concat__fragment('example.com cacert') } - it { is_expected.to contain_concat__fragment('example.com cert') } - it { is_expected.to contain_concat__fragment('example.com key') } - it { is_expected.to contain_concat__fragment('example.com dhparams') } + # for the config file + it { is_expected.to contain_concat('/etc/hitch/hitch.conf') } + it { is_expected.to contain_concat__fragment('hitch::domain example.com') } + end - # for the config file - it { is_expected.to contain_concat('/etc/hitch/hitch.conf') } - it { is_expected.to contain_concat__fragment('hitch::domain example.com') } + context 'with content parameters' do + let(:params) do + { + key_content: key, + cert_content: cert, + } end - context 'mandatory parameters' do - let(:params) do - { - 'cert_source' => '/tmp/cert.pem', - 'key_source' => '/tmp/key.pem', - } - end + it { is_expected.to compile } + it { is_expected.to contain_hitch__domain('example.com') } - it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_hitch__domain('example.com') } + # for the pem file + it { is_expected.to contain_concat('/etc/hitch/example.com.pem') } + it { is_expected.not_to contain_concat__fragment('example.com cacert') } + it { is_expected.to contain_concat__fragment('example.com cert') } + it { is_expected.to contain_concat__fragment('example.com key') } + it { is_expected.to contain_concat__fragment('example.com dhparams') } - # for the pem file - it { is_expected.to contain_concat('/etc/hitch/example.com.pem') } - it { is_expected.not_to contain_concat__fragment('example.com cacert') } - it { is_expected.to contain_concat__fragment('example.com cert') } - it { is_expected.to contain_concat__fragment('example.com key') } - it { is_expected.to contain_concat__fragment('example.com dhparams') } - - # for the config file - it { is_expected.to contain_concat('/etc/hitch/hitch.conf') } - it { is_expected.to contain_concat__fragment('hitch::domain example.com') } - end + # for the config file + it { is_expected.to contain_concat('/etc/hitch/hitch.conf') } + it { is_expected.to contain_concat__fragment('hitch::domain example.com') } end end end end diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb new file mode 100644 index 0000000..76e93f6 --- /dev/null +++ b/spec/spec_helper_local.rb @@ -0,0 +1,31 @@ +require 'openssl' + +def gen_test_tls_data + subject = '/CN=test' + + key = OpenSSL::PKey::RSA.generate(1024) + cert = OpenSSL::X509::Certificate.new + + cert.subject = OpenSSL::X509::Name.parse(subject) + cert.not_before = Time.now + cert.not_after = Time.now + 3600 + cert.public_key = key.public_key + cert.serial = 0x01 + cert.version = 3 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + + cert.extensions = [ + ef.create_extension('basicConstraints', 'CA:TRUE', true), + ef.create_extension('subjectKeyIdentifier', 'hash'), + ] + + cert.add_extension ef.create_extension('authorityKeyIdentifier', + 'keyid:always,issuer:always') + + cert.sign key, OpenSSL::Digest::SHA256.new + + [key, cert] +end diff --git a/tests/init.pp b/tests/init.pp deleted file mode 100644 index 0de063f..0000000 --- a/tests/init.pp +++ /dev/null @@ -1,12 +0,0 @@ -# The baseline for module testing used by Puppet Labs is that each manifest -# should have a corresponding test manifest that declares that class or defined -# type. -# -# Tests are then run by using puppet apply --noop (to check for compilation -# errors and view a log of events) or by fully applying the test in a virtual -# environment (to compare the resulting system state to the desired state). -# -# Learn more about module testing here: -# http://docs.puppetlabs.com/guides/tests_smoke.html -# -include ::hitch