When managing multiple servers, deploying SSH keys for virtual users through Puppet should be straightforward, but syntax and scoping issues often create roadblocks. The error message you're seeing typically indicates either a syntax error or resource declaration problem in your Puppet manifest.
Let's examine a properly structured implementation. First, we need to properly separate our user definitions and SSH key deployments:
# modules/users/manifests/virtual.pp
class users::virtual {
@user { 'devuser':
ensure => present,
home => '/home/devuser',
managehome => true,
uid => '8001',
gid => '8001',
shell => '/bin/bash',
}
}
# modules/users/manifests/ssh_keys.pp
class users::ssh_keys {
Ssh_authorized_key {
ensure => present,
type => 'ssh-rsa', # Modern systems should use rsa or ed25519
}
ssh_authorized_key { 'devuser_key':
user => 'devuser',
key => 'AAAAB3NzaC1yc2EAAA...',
}
}
# modules/users/manifests/init.pp
class users {
include users::virtual
include users::ssh_keys
realize(User['devuser'])
}
The error in your original code stems from several potential issues:
# Problem 1: Missing dependency declaration
# The ssh_authorized_key resource requires the user to exist first
ssh_authorized_key { 'devuser_key':
user => 'devuser',
key => 'AAAAB...',
require => User['devuser'], # Explicit dependency
}
# Problem 2: Virtual resource not being realized
# In your node definition:
node 'webserver' {
include users
# Alternatively, explicitly realize the virtual user
realize(User['devuser'])
}
For better scalability, consider using Hiera to manage users and keys:
# hiera/common.yaml
users::virtual_users:
devuser:
uid: 8001
gid: 8001
ssh_keys:
- 'ssh-rsa AAAAB3... dev@workstation'
# modules/users/manifests/init.pp
class users (
Hash $virtual_users = {},
) {
$virtual_users.each |String $username, Hash $config| {
@user { $username:
ensure => present,
uid => $config['uid'],
gid => $config['gid'],
managehome => true,
}
$config['ssh_keys'].each |String $key| {
ssh_authorized_key { "${username}_${key}":
user => $username,
key => $key,
type => 'ssh-rsa',
}
}
}
}
After implementing your configuration, validate it with:
puppet parser validate modules/users/manifests/*.pp
puppet apply --noop modules/users/manifests/init.pp
Check the generated authorized_keys file:
cat /home/devuser/.ssh/authorized_keys
When attempting to automate SSH key deployments for virtual users through Puppet, administrators often encounter syntax errors in the resource declarations. The error you're seeing:
err: Could not retrieve catalog: Could not parse for environment production: Syntax error at 'user'; expected '}' at /etc/puppet/modules/users/manifests/ssh_authorized_keys.pp:9
typically indicates a scope or resource reference issue in your Puppet manifests.
Let's examine the key components of your configuration:
# Virtual user declaration
class user::virtual {
@user { "user":
home => "/home/user",
ensure => "present",
groups => ["root","wheel"],
uid => "8001",
password => "SCRAMBLED",
comment => "User",
shell => "/bin/bash",
managehome => "true",
}
}
The main issue lies in how the virtual resources are being realized and how the SSH keys are being associated.
Here's a corrected approach that properly handles virtual users and their SSH keys:
# modules/users/manifests/virtual.pp
class users::virtual {
@user { 'devuser':
ensure => present,
uid => '8001',
gid => '8001',
home => '/home/devuser',
managehome => true,
shell => '/bin/bash',
}
@group { 'devuser':
ensure => present,
gid => '8001',
}
@ssh_authorized_key { 'devuser_key':
ensure => present,
user => 'devuser',
type => 'ssh-rsa',
key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQ...',
}
}
# modules/users/manifests/ops.pp
class users::ops {
realize(
User['devuser'],
Group['devuser'],
Ssh_authorized_key['devuser_key']
)
# Ensure home directory permissions are correct
file { "/home/devuser":
ensure => directory,
owner => 'devuser',
group => 'devuser',
mode => '0700',
require => User['devuser'],
}
file { "/home/devuser/.ssh":
ensure => directory,
owner => 'devuser',
group => 'devuser',
mode => '0700',
require => File["/home/devuser"],
}
}
For more maintainable code with multiple users, consider this pattern:
# modules/users/manifests/account.pp
define users::account(
$uid,
$ssh_keys = [],
$groups = [],
) {
user { $name:
ensure => present,
uid => $uid,
gid => $name,
home => "/home/${name}",
managehome => true,
shell => '/bin/bash',
}
group { $name:
ensure => present,
gid => $uid,
}
file { "/home/${name}/.ssh":
ensure => directory,
owner => $name,
group => $name,
mode => '0700',
require => User[$name],
}
$ssh_keys.each |$key| {
ssh_authorized_key { "${name}_${key['name']}":
ensure => present,
user => $name,
type => $key['type'],
key => $key['key'],
}
}
}
# Example usage in node definition
node 'webserver' {
$dev_users = {
'alice' => {
uid => 1001,
ssh_keys => [
{
name => 'laptop',
type => 'ssh-rsa',
key => 'AAAAB3NzaC1yc2EAAAADAQABAA...'
}
],
groups => ['developers'],
}
}
create_resources('users::account', $dev_users)
}
1. Dependency Ordering: Ensure the .ssh directory exists before keys are deployed:
File["/home/${user}/.ssh"] -> Ssh_authorized_key<| |>
2. Virtual Resource Realization: Always realize both the user and their associated resources:
realize(
User['devuser'],
Group['devuser'],
Ssh_authorized_key['devuser_key']
)
3. Hiera Integration: For larger deployments, manage users through Hiera:
# hiera/users.yaml
users::accounts:
bob:
uid: 1002
ssh_keys:
- name: work_key
type: ssh-rsa
key: 'AAAAB3Nz...'
groups:
- admin
The specific error in your implementation likely stems from either:
- Missing or incorrect resource realization
- Scope issues between the virtual user declaration and SSH key resource
- Incorrect syntax in the ssh_authorized_key declaration