Control ssh access with munki nopkg scripts

Often you want to control settings on client machine with scripts rather than packages. One such example is controlling ssh acces, aka ‘Remote Login’ in the OS X UI.

Note: please consider supporting the author of this site by purchasing one of my books! Thank you!

Having ssh access to the clients allows for remote access for trouble shooting and analysis. You can also remotely start or stop processes, like initiating managedsoftwareupdate right now instead of waiting.

However, since there is potential to abuse ssh, we should restrict ssh access to a subset of users. The OS X UI allows you restrict ssh access to a list of users or groups. In our example we will enable Remote Login and give access to any user with administrative privileges, i.e. the admin group.

You could write the scripts to enable these settings and put them in a so called “payload free package.” However, Munki has a simpler and more flexible way of handling this. Ironically, you create a pkginfo file with the nopkg setting.

Building the Install_Check script

Usually when you create the pkginfo for a package or dmg installation, Munki will analyze the files you give it and create conditions on which to install. If the dmg contains an application Firefox, it will look in /Applications for an application named Firefox and compare versions. If the application on the client is not present or of an older version, Munki will perform the installation.

For configurations other than files or applications, we can provide a script that runs and can tell Munki wether to perform the installation first. It might seem a little odd, but it makes a lot of sense to develop this install_check script first.

We need to check wether

  • Remote Login/ssh is enabled
  • access is not set to ‘All Users’
  • the admin user group is allowed access

Open your favorite text editor and create a new script called ssh_install_check.sh and start typing:

#!/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki export PATH

# this will run as a munki install_check script
# exit status of 0 means install needs to run
# exit status not 0 means no installation necessary

So if the script runs and returns 0 as the exit code, Munki will perform the installation. If the script returns any non-zero code, then Munki will assume everything is alright and not perform the installation.

First thing we want to test for is wether Remote Login/ssh is enabled. The CLI tool systemsetup has a command for that:

systemsetup -getremotelogin

Will return Remote Login: On/Off depending on the status. Testing for this is easy enough. Add these lines to the script:

# Is SSH enabled
if [[ $(systemsetup -getremotelogin) = 'Remote Login: Off' ]]; then
    echo 'Remote login is off!'
    exit 0
fi

You can save and test the script on your Mac right now. Use the UI in System Preferences > Sharing to toggle Remote Login and see what the script returns.

Next we want to test wether the access is set to ‘All Users’. There is a group called com.apple.access_ssh on your Mac that contains the users which are allowed ssh access. However, if the access is set to ‘All Users’ the OS renames this group to com.apple.access_ssh-disabled. Add these lines to the script:

ssh_group="com.apple.access_ssh"

# Does a group named "com.apple.access_ssh" exist?
if [[ $(dscl /Local/Default list /Groups | grep "${ssh_group}-disabled" | wc -l) -eq 1 ]]; then
    echo "access set to 'All Users'"
    exit 0
elif [[ $(dscl /Local/Default list /Groups | grep "$ssh_group" | wc -l) -eq 0 ]]; then
    echo "no group '$ssh_group'"
    exit 0
fi

First we defined a variable with the name ofthe group test for, this saves a lot of typing, reduces errors and makes the code more re-usable incase you want to use this for other control groups as well.

Then we run dscl /Local/Default list Groups and grep for the names to see if these groups exist. Then we do the same again in case the group does not exist at all (it really should, but it cannot hurt to test).

You can save and run the script to test again. Switch from ‘All Users’ to ‘Only these users’ in the UI. and see the results of the script.

Finally we want to test wether the admin group is allowed for ssh. We need to see if the group ‘admin’ is contained in the access_ssh group. This is a bit harder thatn it sounds. We can list nested groups using dscl /Local/Default read Groups/com.apple.access_ssh NestedGroups, but this will list long UUID strings, not the names of the groups.

To get the UUID of the admin we can use dsmemberutil getuuid -G admin. and then rest is easy. Add these lines to your script:

# does the group contain the admin group?
admin_uuid=$(dsmemberutil getuuid -G admin)
if [[ $(dscl /Local/Default read Groups/com.apple.access_ssh NestedGroups | grep "$admin_uuid" | wc -l) -eq 0 ]]; then
    echo 'admin group not nested in $ssh_group!'
    exit 0
fi

Sidenote 1: this will only test wether the admin group is directly nested in the access_ssh group. You could have another group in the access_ssh group, which itself contains the admin group. So the admin group would have the privilege to use ssh, but this script would still fail. You could also have all members of the admin group listed directly in the access_ssh group, and the script would not check for that. Either way the script will fail and cause Munki to add the admin group (we will learn how later) and it wouldn’t change access, so we are ok with this somewhat superficial test.
Sidenote 2: this script will also ignore other groups or users in the access_ssh group. If you wanted to enforce stricter access policies, you might want to test wether admin is the only group nested in access_ssh.

Finally the script should return a non-zero value when all the tests are passed. Our final script will look like this (cleaning it up a bit):

#!/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki export PATH

# this will run as a munki install_check script
# exit status of 0 means install needs to run
# exit status not 0 means no installation necessary

ssh_group="com.apple.access_ssh"

# Is SSH enabled
if [[ $(systemsetup -getremotelogin) = 'Remote Login: Off' ]]; then
    echo 'Remote login is off!'
    exit 0
fi

# Does a group named "com.apple.access_ssh" exist?
if [[ $(dscl /Local/Default list /Groups | grep "${ssh_group}-disabled" | wc -l) -eq 1 ]]; then
    echo "access set to 'All Users'"
    exit 0
elif [[ $(dscl /Local/Default list /Groups | grep "$ssh_group" | wc -l) -eq 0 ]]; then
    echo "no group '$ssh_group'"
    exit 0
fi

# does the group contain the admin group?
admin_uuid=$(dsmemberutil getuuid -G admin)
if [[ $(dscl /Local/Default read Groups/com.apple.access_ssh NestedGroups | grep "$admin_uuid" | wc -l) -eq 0 ]]; then
    echo 'admin group not nested in $ssh_group!'
    exit 0
fi

echo "everything seems as it should be, no install needed"
exit 1

Building the postinstall_script

Now that we have a script that tests wether we need to change settings, we can start building the actual script to do the changes. The good news is that while writing the install_check script we already built the outline for the actual install script. We use the same same tests, but instead of merely reporting and exiting, we now perform the necessary change:

#!/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/munki export PATH

ssh_group="com.apple.access_ssh"

# enable ssh
if [[ $(systemsetup -getremotelogin) = 'Remote Login: Off' ]]; then
    echo "turning on Remote Login/SSH"
    systemsetup -setremotelogin On
fi

# Does a group named "com.apple.access_ssh" exist?
if [[ $(dscl /Local/Default list /Groups | grep "${ssh_group}-disabled" | wc -l) -eq 1 ]]; then
    #rename this group
    echo "renaming group '${ssh_group}-disabled'"
    dscl localhost change /Local/Default/Groups/${ssh_group}-disabled RecordName ${ssh_group}-disabled $ssh_group
elif [[ $(dscl /Local/Default list /Groups | grep "$ssh_group" | wc -l) -eq 0 ]]; then
    # create group
    echo "creating group $ssh_group"
    dseditgroup -o create -n "/Local/Default" -r "Remote Login Group" -T group $ssh_group
fi

# does the group contain the admin group?
admin_uuid=$(dsmemberutil getuuid -G admin)
if [[ $(dscl /Local/Default read Groups/$ssh_group NestedGroups | grep "$admin_uuid" | wc -l) -eq 0 ]]; then
    echo "adding admin group to $ssh_group"
    dseditgroup -o edit -n "/Local/Default" -a admin -t group $ssh_group
fi

exit 0

Note that we use dseditgroup instead of manipulating the NestedGroup property with dscl. This is the official and safe way to manipulate groups in OS X.

Save this as ssh_postinstall.sh. Open the Remote Login Sharing UI in System Preferences > Sharing, change the settings and then run this script. To see the changes from the script reflected in the Preference you have to quit and restart System Preferences.

Building the pkginfo file

Now we that we have a script to test and another to change the settings we have to build a pkginfo file that will explain all of this to Munki. Let’s use makepkginfo to get us started:

makepkginfo --name EnableSSH --nopkg --pkgvers=1.0 --installcheck_script=ssh_install_check.sh --postinstall_script=ssh_postinstall.sh --unattended_install > enableSSH.pkginfo

--nopkg sets the install_type and tells Munki that this item has no pkg or dmg file associated with it. The two scripts we built are included in the plist. Munki will read this pkginfo and execute the code when necessary.

You can change or add some more keys (such as description or displayname) and then copy the file to your Munki repository and run makecatalogs. Then add EnableSSH to a manifest on your test machine(s) and run Managed Software Update on it. EnableSSH should appear and run the postinstall script. Check wether SSH works now.

Further, you can go in the UI and change some setting for Remote login, then run Managed Software Update again (or logout to make it run). Notice how our scripts detect that the settings changed from what they are supposed to be and re-ran our postinstall script to re-set them.

more options

You could add a third script to the pkginfo to ‘uninstall’ the settings. That way you could revert your settings to the default. There is even an option for a uninstallcheck_script that will check wether Munki should uninstall your settings.

Summary

The nopkg option allows us to run scripts with Munki, without need to bundle a pkg or dmg payload. Using an installcheck_script allows us to give Munki very precise instructions on when to run the script and repeatedly enforce configurations.

Leave a Reply

Your email address will not be published. Required fields are marked *