Managing users' SSH access
A sensible approach to access control for servers is to use named user accounts with passphrase-protected SSH keys, rather than having users share an account with a widely known password. Puppet makes this easy to manage thanks to the built-in ssh_authorized_key
type.
To combine this with virtual users, as described in the previous section, you can create a define
, which includes both the user
and ssh_authorized_key
resources. This will also come in handy when adding customization files and other resources to each user.
How to do it...
Follow these steps to extend your virtual users' class to include SSH access:
- Create a new module
ssh_user
to contain ourssh_user
definition. Create themodules/ssh_user/manifests/init.pp
file as follows:define ssh_user($key,$keytype) { user { $name: ensure => present, } file { "/home/${name}": ensure => directory, mode => '0700', owner => $name, require => User["$name"] } file { "/home/${name}/.ssh": ensure => directory, mode => '0700', owner => "$name", require => File["/home/${name}"], } ssh_authorized_key { "${name}_key": key => $key, type => "$keytype", user => $name, require => File["/home/${name}/.ssh"], } }
- Modify your
modules/user/manifests/virtual.pp
file, comment out the previous definition for userthomas
, and replace it with the following:@ssh_user { 'thomas': key => 'AAAAB3NzaC1yc2E...XaWM5sX0z', keytype => 'ssh-rsa' }
- Modify your
modules/user/manifests/sysadmins.pp
file as follows:class user::sysadmins { realize(Ssh_user['thomas']) }
- Modify your
site.pp
file as follows:node 'cookbook' { include user::virtual include user::sysadmins }
- Run Puppet:
cookbook# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1413254461' Notice: /Stage[main]/User::Virtual/Ssh_user[thomas]/File[/home/thomas/.ssh]/ensure: created Notice: /Stage[main]/User::Virtual/Ssh_user[thomas]/Ssh_authorized_key[thomas_key]/ensure: created Notice: Finished catalog run in 0.11 seconds
How it works...
For each user in our user::virtual
class, we need to create:
- The user account itself
- The user's home directory and
.ssh
directory - The user's
.ssh/authorized_keys
file
We could declare separate resources to implement all of these for each user, but it's much easier to create a definition instead, which wraps them into a single resource. By creating a new module for our definition, we can refer to ssh_user
from anywhere (in any scope):
define ssh_user ($key, $keytype) { user { $name: ensure => present, }
After we create the user, we can then create the home directory; we need the user first so that when we assign ownership, we can use the username, owner => $name
:
file { "/home/${name}": ensure => directory, mode => '0700', owner => $name, require => User["$name"] }
Tip
Puppet can create the users' home directory using the managehome
attribute to the user resource. Relying on this mechanism is problematic in practice, as it does not account for users that were created outside of Puppet without home directories.
Next, we need to ensure that the .ssh
directory exists within the home directory of the user. We require the home directory, File["/home/${name}"]
, since that needs to exist before we create this subdirectory. This implies that the user already exists because the home directory required the user:
file { "/home/${name}/.ssh": ensure => directory, mode => '0700', owner => $name , require => File["/home/${name}"], }
Finally, we create the ssh_authorized_key
resource, again requiring the containing folder (File["/home/${name}/.ssh"]
). We use the $key
and $keytype
variables to assign the key and type parameters to the ssh_authorized_key
type as follows:
ssh_authorized_key { "${name}_key": key => $key, type => "$keytype", user => $name, require => File["/home/${name}/.ssh"], } }
We passed the $key
and $keytype
variables when we defined the ssh_user
resource for thomas
:
@ssh_user { 'thomas': key => 'AAAAB3NzaC1yc2E...XaWM5sX0z', keytype => 'ssh-rsa' }
Tip
The value for key
, in the preceding code snippet, is the ssh key's public key value; it is usually stored in an id_rsa.pub
file.
Now, with everything defined, we just need to call realize
on thomas
for all these resources to take effect:
realize(Ssh_user['thomas'])
Notice that this time the virtual resource we're realizing is not simply the user
resource, as before, but the ssh_user
defined type we created, which includes the user and the related resources needed to set up the SSH access:
Notice: /Stage[main]/User::Virtual/Ssh_user[thomas]/User[thomas]/ensure: created Notice: /Stage[main]/User::Virtual/Ssh_user[thomas]/File[/home/thomas]/ensure: created Notice: /Stage[main]/User::Virtual/Ssh_user[thomas]/File[/home/thomas/.ssh]/ensure: created Notice: /Stage[main]/User::Virtual/Ssh_user[thomas]/Ssh_authorized_key[thomas_key]/ensure: created
There's more...
Of course, you can add whatever resources you like to the ssh_user
definition to have Puppet automatically create them for new users. We'll see an example of this in the next recipe, Managing users' customization files.