diff --git a/REFERENCE.md b/REFERENCE.md index 0632859..fa4a9b7 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -369,6 +369,7 @@ The following parameters are available in the `openssl::certificate::x509` defin * [`encrypted`](#-openssl--certificate--x509--encrypted) * [`ca`](#-openssl--certificate--x509--ca) * [`cakey`](#-openssl--certificate--x509--cakey) +* [`cakey_password`](#-openssl--certificate--x509--cakey_password) ##### `ensure` @@ -648,6 +649,14 @@ provided. Default value: `undef` +##### `cakey_password` + +Data type: `Optional[Variant[Sensitive[String[1]], String[1]]]` + +Optional password that has encrypted the CA key. + +Default value: `undef` + ### `openssl::config` Generates an openssl.conf file using defaults @@ -1312,6 +1321,7 @@ The following parameters are available in the `x509_cert` type. * [`authentication`](#-x509_cert--authentication) * [`ca`](#-x509_cert--ca) * [`cakey`](#-x509_cert--cakey) +* [`cakey_password`](#-x509_cert--cakey_password) * [`csr`](#-x509_cert--csr) * [`days`](#-x509_cert--days) * [`force`](#-x509_cert--force) @@ -1338,6 +1348,10 @@ The optional ca certificate filepath The optional ca private key filepath +##### `cakey_password` + +The optional CA key password + ##### `csr` The optional certificate signing request path diff --git a/lib/puppet/provider/x509_cert/openssl.rb b/lib/puppet/provider/x509_cert/openssl.rb index e5262a7..997d7da 100644 --- a/lib/puppet/provider/x509_cert/openssl.rb +++ b/lib/puppet/provider/x509_cert/openssl.rb @@ -99,7 +99,10 @@ def create '-out', resource[:path] ] end - options << ['-passin', "pass:#{resource[:password]}"] if resource[:password] + + password = resource[:cakey_password] || resource[:password] + + options << ['-passin', "pass:#{password}"] if password options << ['-extensions', 'v3_req'] if resource[:req_ext] != :false openssl options end diff --git a/lib/puppet/type/x509_cert.rb b/lib/puppet/type/x509_cert.rb index be58061..68ea9b1 100644 --- a/lib/puppet/type/x509_cert.rb +++ b/lib/puppet/type/x509_cert.rb @@ -78,6 +78,10 @@ desc 'The optional ca private key filepath' end + newparam(:cakey_password) do + desc 'The optional CA key password' + end + autorequire(:file) do self[:template] end diff --git a/manifests/certificate/x509.pp b/manifests/certificate/x509.pp index 2aae7c4..6918bfa 100644 --- a/manifests/certificate/x509.pp +++ b/manifests/certificate/x509.pp @@ -93,6 +93,8 @@ # @param cakey # Path to CA private key for signing. Undef mean no CAkey will be # provided. +# @param cakey_password +# Optional password that has encrypted the CA key. # # @example basic usage # @@ -116,37 +118,38 @@ # and set $key_mode to '0640'. # define openssl::certificate::x509 ( - Enum['present', 'absent'] $ensure = present, - Optional[String] $country = undef, - Optional[String] $organization = undef, - Optional[String] $unit = undef, - Optional[String] $state = undef, - Optional[String] $commonname = undef, - Optional[String] $locality = undef, - Array $altnames = [], - Array $extkeyusage = [], - Optional[String] $email = undef, - Integer $days = 365, - Stdlib::Absolutepath $base_dir = '/etc/ssl/certs', - Stdlib::Absolutepath $cnf_dir = $base_dir, - Stdlib::Absolutepath $crt_dir = $base_dir, - Stdlib::Absolutepath $csr_dir = $base_dir, - Stdlib::Absolutepath $key_dir = $base_dir, - Stdlib::Absolutepath $cnf = "${cnf_dir}/${name}.cnf", - Stdlib::Absolutepath $crt = "${crt_dir}/${name}.crt", - Stdlib::Absolutepath $csr = "${csr_dir}/${name}.csr", - Stdlib::Absolutepath $key = "${key_dir}/${name}.key", - Integer $key_size = 3072, - Variant[String, Integer] $owner = 'root', - Variant[String, Integer] $group = 'root', - Variant[String, Integer] $key_owner = $owner, - Variant[String, Integer] $key_group = $group, - Stdlib::Filemode $key_mode = '0600', - Optional[String] $password = undef, - Boolean $force = true, - Boolean $encrypted = true, - Optional[Stdlib::Absolutepath] $ca = undef, - Optional[Stdlib::Absolutepath] $cakey = undef, + Enum['present', 'absent'] $ensure = present, + Optional[String] $country = undef, + Optional[String] $organization = undef, + Optional[String] $unit = undef, + Optional[String] $state = undef, + Optional[String] $commonname = undef, + Optional[String] $locality = undef, + Array $altnames = [], + Array $extkeyusage = [], + Optional[String] $email = undef, + Integer $days = 365, + Stdlib::Absolutepath $base_dir = '/etc/ssl/certs', + Stdlib::Absolutepath $cnf_dir = $base_dir, + Stdlib::Absolutepath $crt_dir = $base_dir, + Stdlib::Absolutepath $csr_dir = $base_dir, + Stdlib::Absolutepath $key_dir = $base_dir, + Stdlib::Absolutepath $cnf = "${cnf_dir}/${name}.cnf", + Stdlib::Absolutepath $crt = "${crt_dir}/${name}.crt", + Stdlib::Absolutepath $csr = "${csr_dir}/${name}.csr", + Stdlib::Absolutepath $key = "${key_dir}/${name}.key", + Integer $key_size = 3072, + Variant[String, Integer] $owner = 'root', + Variant[String, Integer] $group = 'root', + Variant[String, Integer] $key_owner = $owner, + Variant[String, Integer] $key_group = $group, + Stdlib::Filemode $key_mode = '0600', + Optional[String] $password = undef, + Boolean $force = true, + Boolean $encrypted = true, + Optional[Stdlib::Absolutepath] $ca = undef, + Optional[Stdlib::Absolutepath] $cakey = undef, + Optional[Variant[Sensitive[String[1]], String[1]]] $cakey_password = undef, ) { unless $country or $organization or $unit or $state or $commonname { fail('At least one of $country, $organization, $unit, $state or $commonname is required.') @@ -179,15 +182,16 @@ encrypted => $encrypted, } ~> x509_cert { $crt: - ensure => $ensure, - template => $cnf, - csr => $csr, - days => $days, - password => $password, - req_ext => !empty($altnames) or !empty($extkeyusage), - force => $force, - ca => $ca, - cakey => $cakey, + ensure => $ensure, + template => $cnf, + csr => $csr, + days => $days, + password => $password, + req_ext => !empty($altnames) or !empty($extkeyusage), + force => $force, + ca => $ca, + cakey => $cakey, + cakey_password => $cakey_password, } # Set owner of all files diff --git a/spec/defines/openssl_certificate_x509_spec.rb b/spec/defines/openssl_certificate_x509_spec.rb index c3b42d3..e5fe21b 100644 --- a/spec/defines/openssl_certificate_x509_spec.rb +++ b/spec/defines/openssl_certificate_x509_spec.rb @@ -604,4 +604,111 @@ ) } end + + context 'when passing CA properties' do + let(:params) do + { + country: 'com', + organization: 'bar', + commonname: 'baz', + state: 'FR', + locality: 'here', + unit: 'braz', + altnames: ['a.com', 'b.com', 'c.com'], + extkeyusage: %w[serverAuth clientAuth], + email: 'contact@foo.com', + days: 4567, + key_size: 4096, + owner: 'www-data', + ca: '/etc/pki/ca.crt', + cakey: '/etc/pki/ca.key', + cakey_password: '5r$}^', + force: false, + base_dir: '/tmp/foobar', + } + end + + it { + is_expected.to contain_file('/tmp/foobar/foo.cnf').with( + ensure: 'present', + owner: 'www-data' + ).with_content( + %r{countryName\s+=\s+com} + ).with_content( + %r{stateOrProvinceName\s+=\s+FR} + ).with_content( + %r{localityName\s+=\s+here} + ).with_content( + %r{organizationName\s+=\s+bar} + ).with_content( + %r{organizationalUnitName\s+=\s+braz} + ).with_content( + %r{commonName\s+=\s+baz} + ).with_content( + %r{emailAddress\s+=\s+contact@foo\.com} + ).with_content( + %r{extendedKeyUsage\s+=\s+serverAuth,\s+clientAuth} + ).with_content( + %r{subjectAltName\s+=\s+@alt_names} + ).with_content( + %r{DNS\.0\s+=\s+a\.com} + ).with_content( + %r{DNS\.1\s+=\s+b\.com} + ).with_content( + %r{DNS\.2\s+=\s+c\.com} + ) + } + + it { + is_expected.to contain_ssl_pkey('/tmp/foobar/foo.key').with( + ensure: 'present', + password: nil, + size: 4096 + ) + } + + it { + is_expected.to contain_x509_cert('/tmp/foobar/foo.crt').with( + ensure: 'present', + template: '/tmp/foobar/foo.cnf', + csr: '/tmp/foobar/foo.csr', + days: 4567, + ca: '/etc/pki/ca.crt', + cakey: '/etc/pki/ca.key', + cakey_password: '5r$}^', + force: false + ) + } + + it { + is_expected.to contain_x509_request('/tmp/foobar/foo.csr').with( + ensure: 'present', + template: '/tmp/foobar/foo.cnf', + private_key: '/tmp/foobar/foo.key', + password: nil, + force: false + ) + } + + it { + is_expected.to contain_file('/tmp/foobar/foo.key').with( + ensure: 'present', + owner: 'www-data' + ) + } + + it { + is_expected.to contain_file('/tmp/foobar/foo.crt').with( + ensure: 'present', + owner: 'www-data' + ) + } + + it { + is_expected.to contain_file('/tmp/foobar/foo.csr').with( + ensure: 'present', + owner: 'www-data' + ) + } + end end diff --git a/spec/unit/puppet/provider/x509_cert/openssl_spec.rb b/spec/unit/puppet/provider/x509_cert/openssl_spec.rb index 88b42de..8a11059 100644 --- a/spec/unit/puppet/provider/x509_cert/openssl_spec.rb +++ b/spec/unit/puppet/provider/x509_cert/openssl_spec.rb @@ -79,6 +79,29 @@ end end + context 'when using a CA for signing with a password' do + it 'creates a certificate with the proper options' do + resource[:csr] = '/tmp/foo.csr' + resource[:ca] = '/tmp/foo-ca.crt' + resource[:cakey] = '/tmp/foo-ca.key' + resource[:cakey_password] = '5i;6%' + expect(provider_class).to receive(:openssl).with([ + 'x509', + '-req', + '-days', 3650, + '-in', '/tmp/foo.csr', + '-out', '/tmp/foo.crt', + ['-extfile', '/tmp/foo.cnf'], + ['-CAcreateserial'], + ['-CA', '/tmp/foo-ca.crt'], + ['-CAkey', '/tmp/foo-ca.key'], + ['-passin', 'pass:5i;6%'], + ['-extensions', 'v3_req'] + ]) + resource.provider.create + end + end + context 'when forcing key' do it 'exists? should return true if certificate exists and is synced' do resource[:force] = true