Rudder: Fun with variables

How many different configuration files do you maintain for the same service spread over your different Rudder groups?
Personnaly only one!

I hate having to maintain multiple - almost - identical configuration files, it's counterproductive and definitely prone to errors. Having to change one setting in twenty files is always a painful task.
Hopefully Rudder let's you easily manage your config file by keeping the relevant parts of it as variables and i'll show you how to get the most out of it.

Modifying default technique

I started this post so long ago that this point is not even needed anymore. This commit merged it so you should have it by default on any latest Rudder minor version - 2.10.2, 2.9.5 and 2.6.14.

While, in this example, we're going to mainly use genericVariableDefinition and checkGenericFileContent techniques, since Rudder 2.7, you can also set parameters and reuse them as variables inside directives.

Using variables to dynamically change settings in enforced files

sshd_config seems like a good example, i use the same default configuration - applied on all nodes - everywhere with only slight changes in AllowGroups section depending in which group the managed node is located.

Without using variables, i'd have to use at least 10 different directives to apply to my node groups, with actually only a tiny part of a line different from the default...
If for some reason i'd want to change a default setting - not related to my AllowGroups section, let's say PermitRootLogin - i'd then have to modify 10 directives, and this would be painful...
Modifying a directive is at least 3 manual actions + 1 rule generation, so i would end up with 30 manual actions + 10 rule generations.
Not really efficient is it ? And along the way I could just mess one of the file syntax, rendering sshd unavailable for some of my nodes.

How does it look like?

First what do we want to achieve? We want that our sshd_config AllowGroups always contain the group admins and that some node groups also contain the group not_admins.

Let's start by creating a default_admin_group parameter - It's the default you should find everywhere on all your nodes, it makes sense to have it as a parameter.
click on images to get them in fullscreen

Rudder parameters

Now create a new Generic CFEngine variable definition directive with the default priority - 5.
This one will be applied to all your nodes. Generic variable definition 5

Create a second one with priority set to 9 this time - Actually anything higher than 5.
This one will be applied only to the node groups where you want the group not_admins Generic variable definition 9 The higher priority means that this directive will be applied after the first one and consequently override ${generic_variable_definition.SSH_ALLOWED}.

Finally create your default Enforce a file content directive.
Rudder parameters

In plain text

${rudder.rudder_file_edit_header}
# stripped down default sshd config for the sake of example
LoginGraceTime 30s
PermitRootLogin no
StrictModes yes
MaxAuthTries 3
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem   sftp    /usr/libexec/openssh/sftp-server
AllowGroups ${generic_variable_definition.SSH_ALLOWED}

Once applied on your nodes you'll get this

#########################################
### Managed by Rudder, edit with care ###
#########################################
# stripped down default sshd config for the sake of example
LoginGraceTime 30s
PermitRootLogin no
StrictModes yes
MaxAuthTries 3
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem   sftp    /usr/libexec/openssh/sftp-server
AllowGroups admins

Or depending if you apply the second generic variable directive with the priority 9

#########################################
### Managed by Rudder, edit with care ###
#########################################
# stripped down default sshd config for the sake of example
LoginGraceTime 30s
PermitRootLogin no
StrictModes yes
MaxAuthTries 3
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem   sftp    /usr/libexec/openssh/sftp-server
AllowGroups admins not_admins

Now you can just easily add/change settings of your sshd service in a single location.

Even more fun?

You can also set empty values for your variables in the Generic CFEngine variable definition, this will let you insert default empty values replaced by actual content when needed.

Default variable

variable name: SFTP
variable content: 

You set an empty variable here because you need your default configuration to have nothing set. In this example only your SFTP nodes group will have something configured.

For your SFTP nodes group

variable name: SFTP
variable content: Match group sftpusers$(const.n)$(const.t)ChrootDirectory /sftp/$(const.n)$(const.t)ForceCommand internal-sftp

Your default Enforce a file content directive

${rudder.rudder_file_edit_header}
# stripped down default sshd config for the sake of example
LoginGraceTime 30s
PermitRootLogin no
StrictModes yes
MaxAuthTries 3
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem   sftp    /usr/libexec/openssh/sftp-server
AllowGroups ${generic_variable_definition.SSH_ALLOWED}

# Only populated on SFTP servers
${generic_variable_definition.SFTP}

Which would be expanded to

#########################################
### Managed by Rudder, edit with care ###
#########################################
# stripped down default sshd config for the sake of example
LoginGraceTime 30s
PermitRootLogin no
StrictModes yes
MaxAuthTries 3
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem   sftp    /usr/libexec/openssh/sftp-server
AllowGroups admins not_admins

# Only populated on SFTP servers
Match group sftpusers
    ChrootDirectory /sftp/
    ForceCommand internal-sftp

Hope you'll find this article useful and as usual, feel free to contact me if you have any question!