feat: sign ssh host keys
- manage python script/venv to sign ssh host certificates - add approle_id to puppetmaster eyaml files - add class to sign ssh-rsa host keys - add facts to check if the current principals match the desired principals
This commit is contained in:
@@ -32,6 +32,7 @@ class profiles::base (
|
||||
include profiles::ntp::client
|
||||
include profiles::dns::base
|
||||
include profiles::pki::vault
|
||||
include profiles::ssh::sign
|
||||
include profiles::cloudinit::init
|
||||
include profiles::metrics::default
|
||||
include profiles::helpers::node_lookup
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# profiles::helpers::sshsignhost
|
||||
#
|
||||
# wrapper class for python, pip and venv
|
||||
class profiles::helpers::sshsignhost (
|
||||
String $script_name = 'sshsignhost',
|
||||
Stdlib::AbsolutePath $base_path = "/opt/${script_name}",
|
||||
Stdlib::AbsolutePath $venv_path = "${base_path}/venv",
|
||||
Stdlib::AbsolutePath $config_path = "${base_path}/config.yaml",
|
||||
Hash $vault_config = {},
|
||||
String $owner = 'root',
|
||||
String $group = 'root',
|
||||
Boolean $systempkgs = false,
|
||||
String $version = 'system',
|
||||
Array[String[1]] $packages = ['requests', 'pyyaml'],
|
||||
){
|
||||
|
||||
if $::facts['python3_version'] {
|
||||
|
||||
$python_version = $version ? {
|
||||
'system' => $::facts['python3_version'],
|
||||
default => $version,
|
||||
}
|
||||
|
||||
# ensure the base_path exists
|
||||
file { $base_path:
|
||||
ensure => directory,
|
||||
mode => '0755',
|
||||
owner => $owner,
|
||||
group => $group,
|
||||
}
|
||||
|
||||
# create a venv
|
||||
python::pyvenv { $venv_path :
|
||||
ensure => present,
|
||||
version => $python_version,
|
||||
systempkgs => $systempkgs,
|
||||
venv_dir => $venv_path,
|
||||
owner => $owner,
|
||||
group => $group,
|
||||
require => File[$base_path],
|
||||
}
|
||||
|
||||
# install the required pip packages
|
||||
$packages.each |String $package| {
|
||||
python::pip { "${venv_path}_${package}":
|
||||
ensure => present,
|
||||
pkgname => $package,
|
||||
virtualenv => $venv_path,
|
||||
}
|
||||
}
|
||||
|
||||
# create the script from a template
|
||||
file { "${base_path}/${script_name}":
|
||||
ensure => file,
|
||||
mode => '0755',
|
||||
content => template("profiles/helpers/${script_name}.erb"),
|
||||
require => Python::Pyvenv[$venv_path],
|
||||
}
|
||||
|
||||
# create the config from a template
|
||||
file { $config_path:
|
||||
ensure => file,
|
||||
mode => '0660',
|
||||
owner => 'puppet',
|
||||
group => 'root',
|
||||
content => Sensitive(template("profiles/helpers/${script_name}_config.yaml.erb")),
|
||||
require => Python::Pyvenv[$venv_path],
|
||||
}
|
||||
|
||||
# create symbolic link in $PATH
|
||||
file { "/usr/local/bin/${script_name}":
|
||||
ensure => 'link',
|
||||
target => "${base_path}/${script_name}",
|
||||
require => File["${base_path}/${script_name}"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ class profiles::puppet::puppetmaster (
|
||||
include profiles::puppet::autosign
|
||||
include profiles::puppet::gems
|
||||
include profiles::helpers::certmanager
|
||||
include profiles::helpers::sshsignhost
|
||||
include profiles::puppet::server
|
||||
include profiles::puppet::puppetca
|
||||
include profiles::puppet::eyaml
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# profiles::ssh::sign
|
||||
class profiles::ssh::sign (
|
||||
Optional[Array[Stdlib::Host]] $principals = [],
|
||||
){
|
||||
|
||||
# validate and prepare additional alt_names, if any
|
||||
$default_principals = [
|
||||
$::facts['networking']['hostname'],
|
||||
$::facts['networking']['fqdn'],
|
||||
$::facts['networking']['ip'],
|
||||
]
|
||||
$effective_principals = $principals ? {
|
||||
[] => $default_principals,
|
||||
default => concat($default_principals, $principals),
|
||||
}
|
||||
|
||||
# path for the principals file
|
||||
$principals_file = '/etc/ssh/host_principals'
|
||||
|
||||
# alt_names_file contents
|
||||
$principals_file_content = $effective_principals
|
||||
|
||||
# manage the alt names file
|
||||
file { $principals_file:
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => join($principals_file_content, "\n"),
|
||||
}
|
||||
|
||||
# compare the sorted arrays of principals from disk (fact) vs what is intended (this run)
|
||||
$principals_match = sort($::facts['sshd_host_principals']) == sort($principals_file_content)
|
||||
|
||||
# only renew signed certificate if doesnt exist or the principals have changed
|
||||
if ! $::facts['sshd_host_cert_exists'] or ! $principals_match {
|
||||
|
||||
$common_name = $::facts['networking']['fqdn']
|
||||
$valid_hours = '87600h'
|
||||
|
||||
# prepare alt_names and ip_sans arguments conditionally
|
||||
$principals_string = $effective_principals.empty() ? {
|
||||
true => '',
|
||||
default => join($effective_principals, ','),
|
||||
}
|
||||
|
||||
# sshsignhost arguments
|
||||
$cmd = '/usr/local/bin/sshsignhost'
|
||||
$principals_arg = '--valid_principals'
|
||||
$ttl_arg = '--ttl'
|
||||
$public_key_arg = '--public_key'
|
||||
|
||||
# call the script with generate(), capturing json output
|
||||
$json_output = generate(
|
||||
$cmd,
|
||||
$principals_arg,
|
||||
$principals_string,
|
||||
$ttl_arg,
|
||||
$valid_hours,
|
||||
$public_key_arg,
|
||||
"${facts['ssh']['rsa']['type']} ${facts['ssh']['rsa']['key']}",
|
||||
'--json'
|
||||
)
|
||||
$signed_data = parsejson($json_output)
|
||||
|
||||
# manage the signed hostkey file
|
||||
file { '/etc/ssh/ssh_host_rsa_key-cert.pem':
|
||||
ensure => file,
|
||||
content => $signed_data['signed_key'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
}
|
||||
|
||||
}else{
|
||||
# manage the signed hostkey file
|
||||
file { '/etc/ssh/ssh_host_rsa_key-cert.pem':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user