November 21, 2016

Setup secure Munki repository and munki-enroll on Ubuntu 16.04 LTS

I managed several thousand Macintosh desktops in a previous job with the help of Munki, an awesome open-source tool written by Greg Neagle. Alongside Munki, I created munki-enroll, which is now a popular way to manage the automated creation of computer-specific Munki manifests.

I recently worked on a GitHub bug report that was actually not a bug in the munki-enroll code, but instead a problem stemming from a lack of documentation regarding the server-side requirements of munki-enroll. So, without further ado, here’s a proper guide to setting up a Munki repository with munki-enroll on Ubuntu 16.04 LTS.

Following this guide will result in a Apache web server, running PHP with the necessary extensions for munki-enroll, all secured with Basic Authentication and a free SSL certificate from Let’s Encrypt. In this case, the server will be located at

External DNS ( must resolve to the Munki server in order for the Let’s Encrypt certificate bot to generate an SSL certificate successfully.

Update a fresh installation of Ubuntu 16.04 LTS and install all required dependencies. Munki-enroll requires PHP version 5.3 or later and the php-xml extensions package. These commands also install Git (to clone the munki-enroll repository), the Let’s Encrypt certificate bot and the Apache webserver and required utilities:

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y apache2 php git python-letsencrypt-apache apache2-utils libapache2-mod-php php-xml

Once Ubuntu is updated and the required packages are installed, run the Let’s Encrypt certificate bot to setup SSL on the Apache installation. The following command automatically agrees to the Let’s Encrypt Terms of Service and redirects all non-HTTPS traffic to HTTPS. Substitute in the address of the Munki server and a good administrator email:

sudo letsencrypt --apache -d -n --email --agree-tos --redirect

Next, create the folder structure for the Munki repository:

sudo mkdir -p /var/www/html/munki_repo/{catalogs,manifests,pkgs,pkgsinfo}

Then clone munki-enroll to /tmp using Git. Move the munki-enroll server scripts to the correct location:

git clone /tmp/munki-enroll
sudo mv /tmp/munki-enroll/munki-enroll /var/www/html/munki_repo

Ensure Apache can write to the Munki repository:

sudo chmod -R a+rX,g+w /var/www/html/munki_repo
sudo chown -R www-data:www-data /var/www/html/munki_repo

Create a password file containing a user called munki. Enter a password when prompted:

sudo htpasswd -c /etc/apache2/.htpasswd munki

Modify the Let’s Encrypt-created Apache virtual host located at /etc/apache2/sites-enabled/000-default-le-ssl.conf. Look for the default DocumentRoot configuration:

DocumentRoot /var/www/html

Change DocumentRoot to the root of the Munki repository:

DocumentRoot /var/www/html/munki_repo

Paste the following at the bottom of the configuration file to setup the Munki repository with Basic Authentication:

<Directory "/var/www/html/munki_repo">
  AuthType Basic
  AuthName "Restricted Content"
  AuthUserFile /etc/apache2/.htpasswd
  Require valid-user

Save the configuration file and restart Apache:

sudo service apache2 restart

That’s it! Navigate to the repostory located at The web browser should redirect to and prompt for the username and password created earlier.

Additional Notes

Let’s Encrypt Certificate Renewal

Let’s Encrypt certificates are only valid for 90 days. It would be wise to setup a cron job to automatically renew the certificates when necessary.

To setup certificate renewal, launch the root crontab in an editor:

sudo crontab -e

If you have not edited the crontab before, you may be prompted to select an editor. Per the instructions, nano is probably the easiest choice:

no crontab for root - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.tiny

Choose 1-3 [2]: 

Add the following to the crontab and save:

00 1 * * 1 /usr/bin/letsencrypt renew >> /var/log/letsencrypt-renew.log

The Let’s Encrypt certificate bot will now check for a renewed certificate at 1 a.m. every Monday.

Why no SMB/CIFS/AFP access?

In my opinion, setting up SMB/CIFS (Samba) or AFP (Netatalk) for a simple Munki repository is more hassle than it is worth. Also, it may be preferrable to host the Munki repository with an external hosting provider depending on requirements. In that case, it would not be safe to have file sharing protocols open over the Internet.

My recommendation would be to use SSHFS and FUSE for macOS to mount the Munki repository to administrative machines securely over SSH.

September 8, 2016

Fix AutoKey pasting blank lines in Ubuntu

I have been using AutoKey on my Linux desktop to provide automated text insertion capabilities similar to what I get from AutoHotKey on my Windows systems. I prefer to keep things like my email and ticket signatures stored as hotkeys so I can more granularly choose when to use them.

The version of AutoKey included in the Ubuntu repositories has an issue where blank lines are inserted rather than the expected text.

Googling yielded the solution from an AskUbuntu post.

To fix the blank line issue, create a file named org.autokey.service at /usr/share/dbus-1/services with the following content:

[D-BUS Service] 

After creating the file with the content above, kill and restart the AutoKey process or just reboot your computer. AutoKey should now properly insert text instead of blank lines!

D-BUS handles all of the communication between different processes running on the Linux system. It appears the D-BUS service for AutoKey is not configured correctly upon installation.

August 31, 2016

Redirecting the root RDS Web Access URL to the login screen

The root RDS Web Access URL (e.g. does not redirect to the RDS login screen by default in a Windows Server 2012R2 RDS farm setup. Thankfully, there is a simple change in Microsoft IIS to enable this functionality.

In the IIS Manager on each RDS Web Access server in your farm, select “Default Web Site” from the left pane, and select “HTTP Redirect” from the “Default Web Site Home” pane.

RDS Web Access Redirect

Check “Redirect requests to this destination:” and enter Check “Only redirect requests to content is this directory (not subdirectories)” and set the “Status code” dropdown to Found (302).

RDS Web Access Redirect

The root URL should now redirect to the RDS Web Access login page.

August 30, 2016

Highly-available Microsoft Remote Desktop Services Farm diagram

I stood up my first Microsoft Remote Desktop Services several months ago, with a great deal of help from TheWolfBlog’s great guide.

One thing I didn’t have when I started my work was a good illustration of how the final highly-available infrastructure would look. I’ve included an image of a sample setup below, and I hope it benefits someone.

In the image below, the firewalls represent load balancers.

Highly-Available RDS Farm

August 29, 2016

Dealing with compromised accounts in Exchange Online

We have lately had a rash of compromised email accounts in our Office 365 Exchange Online infrastructure. It appears a well-crafted phishing email caught at least a small percentage of our 100,000-plus mailboxes.

The outbound SPAM protection in Office 365 and Exchange Online is very robust. Suspected SPAM messages are sent through a high-risk pool of IP addresses, and accounts are limited to 10,000 outbound messages per day before being blocked by the anti-SPAM intelligence. A support ticket must be filed with Microsoft to reactivate an account once it is blocked from sending outbound mail.

Normally, the anti-SPAM alerts received when an account hits the outbound message limit are sufficient for administrative notification. The most recent set of spammers, however, have been intelligently working underneath this notification system by sending less than 10,000 messages daily. The spammers instead cover their tracks by setting up email forwarding or an inbox rule to hide any bouncebacks from the slew of outbound junk.

Email forwarders victimized the most recent compromised accounts. These accounts came into the help desk with the same symptom of not receiving email messages. A look at the mailbox through Exchange Online PowerShell reveals the cause:

PowerShell Prompt

The spammer set the mailbox to forward all mail to an external address under their control, thereby hiding the nefarious activities.

To remove the forwarder, run the following in PowerShell:

Set-Mailbox -ForwardingSmtpAddress $Null

In some cases, spammers will instead setup an inbox rule to hide their activity. View all inbox rules for a mailbox through PowerShell by running the following:

Get-InboxRule -Mailbox

For a clean mailbox, this should return nothing or return valid inbox rules created by the customer.

It never hurts to educate your customers on never giving out login information through an email!

May 5, 2016

Add permission to Public Folder recursively with PowerShell

We had a request to add permissions for a customer throughout a deeply nested structure in our Exchange Online Public Folders.

These commands will not override or change permissions where they are already set.

Connect to Exchange Online PowerShell

$Cred = Get-Credential
Connect-MSOLService -Credential $Cred
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri -Credential $Cred -Authentication Basic -AllowRedirection
Import-PSSession $Session

Add Permissions and Verify Success

In PowerShell, run:

Get-PublicFolder -Identity "\Folder Name" -Recurse | Add-PublicFolderClientPermission -User jsmith -AccessRights Owner

Verify the change was successful:

Get-PublicFolder -Identity "\Folder Name" -Recurse | Get-PublicFolderClientPermission | Where-Object { $PSItem.User -like "SMITH*" }

Use the customer’s name in the Where-Object cmdlet. All of our accounts are named in a “LASTNAME, FIRSTNAME” format, and the command reflects that. This command will print all of the customer’s rights through the tree.