Skip to main content
Jamf Nation, hosted by Jamf, is a dynamic and knowledgeable community of Apple-focused IT admins and Jamf Pro users. Join us in person, in October, for the annual Jamf Nation User Conference (JNUC) to discover new and better ways to manage Apple devices.
CCT Badge
7

Script assistance - 802.1x wifi / user keychain

Posted: 8/11/17 at 6:51 PM by bbot

Long story short, a few months ago we changed CA's and found ourselves stuck with updating the existing wifi configuration profile. We ended up having to change the entire company's wifi SSID to get around this. (more info here - https://www.jamf.com/jamf-nation/discussions/23826/config-profile-question)

We ultimately decided to deploy the wifi config profile through a script using the "profiles -I -F" command instead of configuration profile for fear of getting stuck in the same situation. While the cutover to the new CA server went relatively seamless, we don't have a seamless way of updating the wireless certificates. (it only works if the user goes through Self Service)

What I'm looking for:
I'm looking for a way to have seamless wifi certificate renewals.

Here's the problem:
Running the script as the logged on user through Self Service works 100%. It'll remove the old wifi cert, download a new wifi cert, then It'll create the com.apple.network.eap.user.identity.wlan.ssid.SSIDHERE in the user's keychain properly.

When pushing the same exact script from Casper, it'll remove the old wifi cert, download a new wifi ciert, but the com.apple.network.eap.system.identity.wlan.ssid.SSIDHERE keychain entry is created in the system keychain. The user is unable to connect to wifi with this setting.

I've tried multiple variations of things for days and can't get it to work. How are people managing 802.1x wifi profiles and how are you renewing them?

#!/bin/sh

#  Wifi Self Service.sh
#  Script to connect to SSID
#  Brandon Wong 4/24/2017

function connectWifi (){
/usr/bin/profiles -I -F /Library/LC/LC-#.mobileconfig
    Cert="$compname.DOMAIN.com"
    #security set-identity-preference -n -s "com.apple.network.eap.user.identity.wlan.ssid.SSID" /Library/Keychains/System.keychain
    security set-identity-preference -c "$Cert" -s "com.apple.network.eap.user.identity.wlan.ssid.SSID" /Library/Keychains/System.keychain
    #sudo security set-identity-preference -c "$Cert" -s "com.apple.network.eap.user.identity.wlan.ssid.SSID" /Library/Keychains/System.keychain
wservice=`/usr/sbin/networksetup -listallnetworkservices | grep -Ei '(Wi-Fi|AirPort)'`
whwport=`networksetup -listallhardwareports | awk "/$wservice/,/Ethernet Address/" | awk 'NR==2' | cut -d " " -f 2`
hwports=`networksetup -listallhardwareports | awk '/Hardware Port: Wi-Fi/,/Ethernet/' | awk 'NR==2' | cut -d " " -f 2`
wirelessnw=`networksetup -getairportnetwork $hwports | cut -d " " -f 4`
    sleep 10
    if [[ $wirelessnw == "SSID" ]]; then
    echo "Connected to SSID"
    else
    echo "Machine is not connected to SSID. Checking identity preference"
    security get-identity-preference -s "com.apple.network.eap.user.identity.wlan.ssid.SSID" /Library/Keychains/System.keychain
    fi
}

function bindToDomain (){
# Unbind machine from domain
 dsconfigad -remove -force -username macimaging -password $Pass
    sleep 10
    echo "Unbinding"
    killall opendirectoryd
    sleep 5

    ## Testing has shown that unbinding twice may be necessary. 
    dsconfigad -remove -force -username username -password $Pass &> /dev/null
    sleep 10
    echo "Unbinding twice just incase"

    ## Begin rebinding process

    #Basic variables
    computerid=`scutil --get LocalHostName`
    domain=SSID.us
    udn=
    OU="CN=Computers,DC=Corp,DC=#,DC=com"

    #Advanced variables
    alldomains="disable"
    localhome="enable"
    protocol="smb"
    mobile="enable"
    mobileconfirm="disable"
    user_shell="/bin/bash"
    admingroups="Corp\"
    namespace="domain"
    packetsign="allow"
    packetencrypt="allow"
    useuncpath="disable"
    passinterval="90"

    # Bind to AD
    dsconfigad -add $domain -alldomains $alldomains -username $udn -password $Pass -computer $computerid -ou "$OU" -force -packetencrypt $packetencrypt
    sleep 1
    echo "Rebinding to AD and setting advanced options"

    #set advanced options
    dsconfigad -localhome $localhome
    sleep 1
    dsconfigad -groups "$admingroups"
    sleep 1
    dsconfigad -mobile $mobile
    sleep 1
    dsconfigad -mobileconfirm $mobileconfirm
    sleep 1
    dsconfigad -alldomains $alldomains
    sleep 1
    dsconfigad -useuncpath "$useuncpath"
    sleep 1
    dsconfigad -protocol $protocol
    sleep 1
    dsconfigad -shell $user_shell
    sleep 1
    dsconfigad -passinterval $passinterval
    sleep 1

    #dsconfigad adds "All Domains"
    # Set the search paths to "custom"
    dscl /Search -create / SearchPolicy CSPSearchPath
    dscl /Search/Contacts -create / SearchPolicy CSPSearchPath

    sleep 1

    # Add the "corp.tlcinternal.us" search paths
    dscl /Search -append / CSPSearchPath "/Active Directory/CORP/DOMAIN.com"
    dscl /Search/Contacts -append / CSPSearchPath "/Active Directory/CORP/DOMAIN.com"

    sleep 1

    # Delete the "All Domains" search paths
    dscl /Search -delete / CSPSearchPath "/Active Directory/CORP/All Domains"
    dscl /Search/Contacts -delete / CSPSearchPath "/Active Directory/CORP/All Domains"
    dscl /Search -delete / CSPSearchPath "/Active Directory/CORP/#.us"
    dscl /Search/Contacts -delete / CSPSearchPath "/Active Directory/CORP/#.us"
    dscl /Search -delete / CSPSearchPath "/Active Directory/CORP/#.us"
    dscl /Search/Contacts -delete / CSPSearchPath "/Active Directory/CORP/#.us"

    sleep 1

    # Restart opendirectoryd
    killall opendirectoryd
    sleep 3

    dependenciesCheck

}

function dependenciesCheck(){

# Check for an AD computer object
ad_computer_name=`dsconfigad -show | grep "Computer Account" | awk '{print $4}'`
compObj=`dscl /Active\ Directory/CORP/All\ Domains read /Computers/$ad_computer_name RecordName | awk '{print $2}'`

if [ $ad_computer_name = $compObj ]; then
    echo Matching AD object exists. Continue script...

    # Make sure mobileconfig exists
    file=/Library/LC/NAMEOFCONFIG.mobileconfig

        if [ -f "$file" ]; then
            connectWifi
                else
                echo Profile missing. Downloading profile
                jamf policy -trigger *TRIGGER*
                    if [ -f "$file" ]; then
                    connectWifi
                        else
                        echo Cannot connect to *SSID*. Mobileconfig is missing.
                        exit 1
        fi
                    fi
else    
    echo "Missing Domain Binding"
    bindToDomain
fi

}

# HARDCODED VALUES ARE SET HERE
Pass=""

# CHECK TO SEE IF VALUES WERE PASSED FOR $4, AND IF SO, ASSIGN THEM
if [ "$4" != "" ] && [ "$Pass" == "" ]; then 
Pass=$4
fi

# Check to make sure Pass variable was passed down from Casper
if [ "$Pass" == "" ]; then 
echo "Error: The parameter 'Pass' is blank. Please specify a value." 
exit 1 
fi

# Variables
currUser=`stat -f "%Su" /dev/console`
echo "Current user is $currUser"

    # Cleanup duplicate machine certificates
    compname=`dsconfigad -show | grep "Computer Account" | awk '{print $4}' | sed 's/.$//'`
    Certs="$compname.#.com"
    hashes=$(security find-certificate -c "$Certs" -a -Z|grep SHA-1|awk '{ print $NF }')
        for hash in $hashes; do
        echo deleting duplicate certs $hash
        sudo security delete-certificate -Z $hash
        done

# Check dependencies 
    dependenciesCheck

exit 0
7
CCA Badge

Posted: 8/12/17 at 4:37 PM by mm2270

When a policy pushes a script using the recurring check in trigger, it's running entirely as root, since it's called by a LaunchDaemon. So it makes sense it is adding the wi-fi setting in the System.keychain.

What you have to do is run any commands in the script that must be run as the logged in user, as the logged in user. This question and situation comes up a lot, probably every week here on average, no exaggeration. If you do searches on how to run commands within a script as the logged in user, you should find 100 threads if you find 1. One of them will have a solution for you.
And just to let you know, it's going to be the security set-identity-preference lines that will need to get run as the user, as I'm sure you guessed already.

CCT Badge

Posted: 8/14/17 at 12:02 AM by bbot

@mm2270 I was under the impression that running Self Service policies are also executed as root, which left me a bit confused.

I've tried variations of /usr/bin/profiles with the -U and sudo -u /usr/bin/profiles command to specify user to no avail. Been trying to figure this out on and off for the past few months and feel like I'm hitting a dead end.

I've tried making sure to wipe the keychain entry -- security set-identity-preference -n -s "com.apple.network.eap.user.identity.wlan.ssid.SSID " /Library/Keychains/System.keychain

Tried setting it with commands similar to -- security set-identity-preference -c "$Cert" -s "com.apple.network.eap.user.identity.wlan.ssid.LC-Authorized" /Library/Keychains/System.keychain sudo security set-identity-preference -c "$Cert" -s "com.apple.network.eap.user.identity.wlan.ssid.LC-Authorized" /Library/Keychains/System.keychain
sudo -u "$currentUser" -i /usr/bin/security set-identity-preference -s com.apple.network.eap.user.identity.wlan.ssid /Library/Keychains/System.keychain


How are you renewing wifi certificates without user interaction with using /usr/bin/profiles?

CCT Badge

Posted: 8/14/17 at 1:38 AM by bbot

To add --
Having the configuration profile set to Computer level generates a computer certificate, just doesn't assign the identity properly.
Having the configuration profile set to User level doesn't generate a cert at all.

CCA Badge

SOLVED Posted: 8/14/17 at 8:49 AM by mm2270

@bbot In your script, you keep specifying the System keychain as the target for the security command. /Library/Keychains/System.keychain Is there a reason why? Maybe I'm misunderstanding, but are you attempting to add the wi-fi identity preference into the System keychain, but have user accounts use that identity preference? If so, I don't think that's going to work. As far as I know, identity preferences need to go into the user's keychains. I'm wondering if that is part of the issue here.

As for us, we don't do the cert renewals with a script. We had a custom agent built for us that communicates with our CMS servers. The agent is local (pushed from Jamf Pro) to all Macs and is run by a LaunchAgent every 30 minutes. It checks to see if the Wi-Fi 802.1x certs for the user are valid. If they are it just exits silently. If they are not, the user is prompted with a GUI window to enter their account password, which gets sent back to CMS and will issue a new set of certs to the device.

However, the one thing this agent will not do is create the Wi-Fi identity preference. We do that with a LaunchAgent that keeps checking periodically to make sure if the certs for the user are present, that there is an accompanying identity preference that pairs up the cert to our Wi-Fi SSID.
But it's not a requirement to use a LaunchAgent for that. You can do this with a script called by root, but need to make sure you are running the command to create the identity preference as the logged in user, otherwise it will add the pref to the System keychain, and as you already found out, clients can't use identity prefs in the system keychain.

Try this variation and see if it helps

#!/bin/bash

loggedInUser=$(stat -f%Su /dev/console)
loggedInUID=$(id -u "$loggedInUser")

/bin/launchctl asuser "$loggedInUID" sudo -iu "$loggedInUser" "/usr/bin/security set-identity-preference -c \"$Cert\" -s \"com.apple.network.eap.user.identity.wlan.ssid.SSID\""
CCT Badge

Posted: 8/14/17 at 11:28 AM by bbot

@mm2270 I have to double check, but I believe why I had to specify /Library/Keychains/System.keychain was because the machine certificate that is requested is placed in the System keychain.

I am trying to have the identity preference in the login keychain, not the system.

Generating the certificate isn't the issue for us, the mobileconfig does that perfectly. It sounds like what you're suggesting to create the identity pref in the user keychain will work...

I've tried /usr/bin/security set-identity-preference -c \"$Cert\" -s \"com.apple.network.eap.user.identity.wlan.ssid.SSID\ as my current logged in user and confirmed this creates the identity preference in the login keychain; however... when I try "sudo -iu "$loggedInUser" "/usr/bin/security set-identity-preference -c \"$Cert\" -s \"com.apple.network.eap.user.identity.wlan.ssid.SSID\""" -- nothing happens.

I'm wondering if there's some security in-place that prevents the creation of keychain items in other user's keychain.

Thanks for your time on this. I really appreciate it.

CCT Badge

Posted: 8/14/17 at 11:56 AM by bbot

Wait, I'm on to something. I wasn't using the UID for "launchctl asuser" Looks like I was able to create the identity in the login keychain when specifying the UID. (i overlooked it and put the user account) Going to test and will post back here.

CCT Badge

Posted: 8/14/17 at 3:31 PM by bbot

@mm2270 It works! The trick was to use launchctl to run it as the user. I owe you big time.

Now I just need to create an EA that can help me determine whether a certificate needs renewing or not, then I can use the profiles command to generate a new certificate from my config profile...then use launchctl and /usr/binsecurity to create the identity preference.

Thanks so much!

On a side note, using the same format you provided didn't work for some reason. /bin/launchctl asuser "$loggedInUID" sudo -iu "$loggedInUser" "/usr/bin/security set-identity-preference -c \"$Cert\" -s \"com.apple.network.eap.user.identity.wlan.ssid.SSID\""

I took out the " and the \ and it works. (it was giving me an error about the file or directory doesn't exist)