How do you reissue/troubleshoot certificates deployed by Config profile?

mbezzo
Contributor III

Hi all,
I'm struggling to come up with a good solution to what I can only imagine is a common one: We use a config profile to deploy a certificate for connecting to our wireless network. Sometimes users delete that issued cert accidentally. Sometimes there are issues and our support staff may need to delete the cert during troubleshooting. But - how do you get a new one issued? The device still has the config profile - it just needs a new cert. Do you Unscope and rescope the specific device (and absolutely positively make sure you don't accidentally click the "apply to all devices" option)? Do you duplicate the config profile and put a copy in Self Service? But then you'd have 2 profiles potentially on the device that do the same thing? Maybe you could remove the device from the scope of the other config profile when the Self Service one is run? But then, down the line, when you may need to update/change this config profile - how do you force it to those few users who now only have the Self Service profile?

Maybe with some fancy API work you could have a button in Self Service that removed the device from the scope, and then re-added it? Is that even possible with the API?

Anyway, been struggling with this one for a bit and very curious to hear how others are approaching and/or dealing with it.

Thank you!
Matt

1 ACCEPTED SOLUTION

sdagley
Esteemed Contributor II

@mbezzo Here's an example of my script to add/remove a computer to/from a static group:

#!/bin/bash

# Add and remove a computer from a group example by @sdagley
# Thanks to unknown author for the example of breaking out the xmlHeader and apiData
# for API calls
# 
# Parameter 4 - JSS URL
# Parameter 5 - Encrypted username for Jamf Pro API access account
# Parameter 6 - Encrypted password for Jamf Pro API access account

jssAddress="$4"

jssAPIUsernameEncrypted="$5"
jssAPIUsernameSalt="UsernameSaltHere"
jssAPIUsernamePassphrase="UsernamePassphraseHere"

jssAPIPasswordEncrypted="$6"
jssAPIPasswordSalt="PasswordSaltHere"
jssAPIPasswordPassphrase="PasswordPassphraseHere"

function DecryptString() {
    # Usage: ~$ DecryptString "Encrypted String" "Salt" "Passphrase"
    echo "${1}" | /usr/bin/openssl enc -aes256 -d -a -A -S "${2}" -k "${3}"
}

jssAPIUsername=$(DecryptString $jssAPIUsernameEncrypted $jssAPIUsernameSalt $jssAPIUsernamePassphrase)
jssAPIPassword=$(DecryptString $jssAPIPasswordEncrypted $jssAPIPasswordSalt $jssAPIPasswordPassphrase)

ComputerName=$(/usr/sbin/scutil --get ComputerName)

apiURL="JSSResource/computergroups/id/"

#XML header stuff
xmlHeader="<?xml version="1.0" encoding="UTF-8"?>"

TargetGroupID="GroupID#Here"
TargetGroupName="GroupNameHere"

# Add computer to a group
apiData="<computer_group><id>${TargetGroupID}</id><name>${TargetGroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"

curl -sSkiu ${jssAPIUsername}:${jssAPIPassword} "${jssAddress}/${apiURL}${TargetGroupID}" 
    -H "Content-Type: text/xml" 
    -d "${xmlHeader}${apiData}" 
    -X PUT

TargetGroupID="GroupID#Here"
TargetGroupName="GroupNameHere"

# Delete computer from a group
apiData="<computer_group><id>${TargetGroupID}</id><name>${TargetGroupName}</name><computer_deletions><computer><name>$ComputerName</name></computer></computer_deletions></computer_group>"

curl -sSkiu ${jssAPIUsername}:${jssAPIPassword} "${jssAddress}/${apiURL}${TargetGroupID}" 
    -H "Content-Type: text/xml" 
    -d "${xmlHeader}${apiData}" 
    -X PUT

exit 0

View solution in original post

27 REPLIES 27

sdagley
Esteemed Contributor II

@mbezzo If you make a Configuration Profile available in Self Service with Allow Removal set to Yes it will remain in Self Service after installation but the button name will change to Remove. If it's still in scope then after it's removed you should then be able to re-install it. That might work for you if you're OK with your users being able to remove the profile on their own.

mm2270
Legendary Contributor III

I don't have a great solution to this, but I acknowledge this can be a problem because of how certs are deployed via a profile. The cert can still be removed, but the profile already did it's job of delivering it initially, so it isn't going to do it again unless you intervene.

The only thing I can think of is, if these certs are machine level and not individual to each user account, you may be able to do something where you can have an Extension Attribute check to see if the cert is in the keychain as it should be, coming back with a simple Yes/No kind of response, and pair that with looking for the profile installed. In your Smart Group you would have to check for the existing of one, but not the other - specifically, Macs that have the Config Profile installed, but are missing the cert would fall into a group you could use for Exclusion from the Config Profile. Meaning, they would get removed from Scope, thereby removing the profile. You would then have another Smart Group that looked for Macs that had neither the Profile or the certificate, and use that as the main scope for the profile that will deliver the certificate. Machines with both cert and profile in place don't get anything redeployed since they are in good standing.

I can't say I've tested anything like the above, but if it works, what would happen is, on a recon, the machines with the profile installed but missing the certificate would move into the group to get the profile removed. When the next inventory check takes place, they should get moved back into scope to have the profile repushed to them.

It's possible none of the above will work as I'm thinking. Again, haven't tried it, but essentially the only way I can think to address this is something like the above - checking for specific conditions and using those for scopes and exclusions.

Maybe someone else has a better idea though.

mbezzo
Contributor III

Hi @sdagley I don't mind if users remove the profile - but it does still leave the "pushed to everyone" config profile on the device. I guess I could turn off the auto deployment of that and stick to a Self Service only config - but man. That just seems like the wrong way. Automating this is better than having to make it fully manual!

thanks,
Matt

m_donovan
Contributor III

What about something like this:

Single script that runs from self service scoped to techs.
Script does the following: remove from Smart or Static group (that profile is scoped to) Recon - removes profile re-add to smart or static group Recon - reinstalls profile and cert.

If the cert is how you get on wifi then they would have to do this while hard wired.

sdagley
Esteemed Contributor II

@mbezzo I have a Policy in Self Service scoped to Macs that aren't connected to the corporate Wi-Fi and that SSID is visible which adds the computer running it to a Static Group which excludes the Mac from the scope of the pushed configuration profile and then adds it to a group that enables the Self Service installable profile. If you only want the pushed configuration profile to be re-installed you could have a script that adds it to a static group that's scoped as an exclusion, delay for a few seconds, then remove it from that list to put it back into scope for the profile. The delay might not be necessary, but I've seen a cases where the profile doesn't get removed and re-installed without one. I'm using a Self Service profile install as pushing a User Level profile won't install immediately while a Self Service profile does.

mbezzo
Contributor III

Thanks @mm2270, I've been tossing some similar ideas around in my head as well. It's rough that seemingly the only options are this complicated! Will keep thinking on it. :)

Thanks again!

mbezzo
Contributor III

@sdagley @m.donovan These are interesting ideas, but I think it'd have to be a static group right? - which begs the question, how do you populate/maintain that static group?

lots to think about - thanks all! I think I'll open a ticket with Apple Enterprise support and see what they say too.

sdagley
Esteemed Contributor II

@mbezzo I create the static groups in the Jamf Pro GUI then use the Jamf API in a script to add & delete computers to the groups as needed.

mbezzo
Contributor III

Ok, I just had a thought. Haven't had a chance to test, but curious if you all think it's possible:

  1. Create a new, empty Static group and add to the Exclusions for the Config Profile
  2. Create a self service item that runs an API call to add that device to the exclusion group, and performs a recon
  3. Wait for config profile removal - The config profile should be removed, right?
  4. A second self service item that runs a similar API call to remove the device from the excluded static group and runs a recon.
  5. Profit?

Crazy? Doable? I suppose with a little scriptage it could be one Self Service item that would wait for the config profile to be removed, then yank the device from the excluded static group.

Hopefully will be able to test tomorrow - but holler if you think there's any glaring reason why this just won't work!

Thanks,
Matt

ryan_ball
Valued Contributor

@mbezzo You can create an extension attribute with a "Text Field" type. This could be populated with specific strings. Through the GUI or through the API you could populate this extension attribute. You could then create a smart group where that extension attribute equals a specific string. You could then use this to unscope/rescope machines for the profile to essentially remove/reinstall the profile easily. You'd have to use exclusions for this to function.

Let me know if you need me to elaborate.

mbezzo
Contributor III

Hi @ryan.ball nope - totally makes sense! Definitely another way to do this.

FWIW, in my very-manual-and-not-yet-scripted testing, adding/removing a device from the static group scoped to be excluded from the config profile works well. So I'm gonna head down this path. Will report back!

Thanks again,
Matt

mbezzo
Contributor III

on that note... if anyone has a chunk of code for adding a device to a static group with the API - do share! :)

Eigger
Contributor III

This happened to me before, but I went on a different route. First, I created a new wireless network using PSK. I created a Wireless Config Profile for this new WiFi Network and deployed it to my devices. I made sure all my devices got the profile by looking at logs and dashboard. I used a script that runs on login and recurring check-in to switch them to new WiFi network, put the new network on index 0, and delete all preferred wireless network except the new one of course. then I unscoped all devices from the 802.1x profile that has an expiring cert. Then I just made a new profile with the new cert and redeploy. then use the same script to switch their wifi back.

mm2270
Legendary Contributor III

Regarding adding computers to a Static Group, this thread may help: https://www.jamf.com/jamf-nation/discussions/30085/how-do-you-put-a-computer-into-a-static-group-wit...

You'll also need to come up with a way to remove the computer from the group, which is a little more work I believe.

jameson
Contributor II

@sdagley How can you make a configuration profile available in self service ? . If i open a config profile there is no self service option available ?- but if it could be put in self service it would be great

EDIT: Yeah ok - found it :)

sdagley
Esteemed Contributor II

@mbezzo Here's an example of my script to add/remove a computer to/from a static group:

#!/bin/bash

# Add and remove a computer from a group example by @sdagley
# Thanks to unknown author for the example of breaking out the xmlHeader and apiData
# for API calls
# 
# Parameter 4 - JSS URL
# Parameter 5 - Encrypted username for Jamf Pro API access account
# Parameter 6 - Encrypted password for Jamf Pro API access account

jssAddress="$4"

jssAPIUsernameEncrypted="$5"
jssAPIUsernameSalt="UsernameSaltHere"
jssAPIUsernamePassphrase="UsernamePassphraseHere"

jssAPIPasswordEncrypted="$6"
jssAPIPasswordSalt="PasswordSaltHere"
jssAPIPasswordPassphrase="PasswordPassphraseHere"

function DecryptString() {
    # Usage: ~$ DecryptString "Encrypted String" "Salt" "Passphrase"
    echo "${1}" | /usr/bin/openssl enc -aes256 -d -a -A -S "${2}" -k "${3}"
}

jssAPIUsername=$(DecryptString $jssAPIUsernameEncrypted $jssAPIUsernameSalt $jssAPIUsernamePassphrase)
jssAPIPassword=$(DecryptString $jssAPIPasswordEncrypted $jssAPIPasswordSalt $jssAPIPasswordPassphrase)

ComputerName=$(/usr/sbin/scutil --get ComputerName)

apiURL="JSSResource/computergroups/id/"

#XML header stuff
xmlHeader="<?xml version="1.0" encoding="UTF-8"?>"

TargetGroupID="GroupID#Here"
TargetGroupName="GroupNameHere"

# Add computer to a group
apiData="<computer_group><id>${TargetGroupID}</id><name>${TargetGroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"

curl -sSkiu ${jssAPIUsername}:${jssAPIPassword} "${jssAddress}/${apiURL}${TargetGroupID}" 
    -H "Content-Type: text/xml" 
    -d "${xmlHeader}${apiData}" 
    -X PUT

TargetGroupID="GroupID#Here"
TargetGroupName="GroupNameHere"

# Delete computer from a group
apiData="<computer_group><id>${TargetGroupID}</id><name>${TargetGroupName}</name><computer_deletions><computer><name>$ComputerName</name></computer></computer_deletions></computer_group>"

curl -sSkiu ${jssAPIUsername}:${jssAPIPassword} "${jssAddress}/${apiURL}${TargetGroupID}" 
    -H "Content-Type: text/xml" 
    -d "${xmlHeader}${apiData}" 
    -X PUT

exit 0

sdagley
Esteemed Contributor II

A word of warning to anyone using Configuration Profiles installed via Self Service - even though the install is manually initiated if the computer falls out of scope for the profile it will automatically be removed.

mbezzo
Contributor III

Thanks @mm2270 and @sdagley That's a great start for getting a script cobbled together!

mbezzo
Contributor III

@sdagley I've got my script (mostly) done - thanks to you! But MAN, I can't figure out what's failing during the api PUT command. I keep getting 401 Unauthorized errors but I'm using an account with full perms (don't worry, it's my test environment!) When I run the script from terminal for testing (while hard coding all the Jamf provided variables) the username/password are decrypting accurately. Any pointers? Here's some sanitized verbose output from the terminal:

++ /usr/sbin/scutil --get ComputerName
+ ComputerName=TestComputerName
+ apiURL=JSSResource/computergroups/id/
+ xmlHeader='<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
+ apiData='<computer_group><id>221</id><name>WiFive Fix (test)</name><computer_additions><computer><name>TestComputerName</name></computer></computer_additions></computer_group>'
+ curl -sSkiu ‘theActualAPIuser:theActualAPIpassword’ https://ourjssurl.com/JSSResource/computergroups/id/221 -H 'Content-Type: text/xml' -d '<?xml version="1.0" encoding="UTF-8" standalone="no"?><computer_group><id>221</id><name>WiFive Fix (test)</name><computer_additions><computer><name>TestComputerName</name></computer></computer_additions></computer_group>' -X PUT

HTTP/1.1 401 Unauthorized
Accept-Ranges: bytes
Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
Content-Type: text/html;charset=UTF-8
Date: Thu, 04 Apr 2019 18:23:38 GMT
Server: Jamf Cloud Node
WWW-Authenticate: Basic realm="Restful JSS Access -- Please supply your credentials"
X-FRAME-OPTIONS: SAMEORIGIN
Content-Length: 424
Connection: keep-alive

<html>
<head>
   <title>Status page</title>
</head>
<body style="font-family: sans-serif;">
<p style="font-size: 1.2em;font-weight: bold;margin: 1em 0px;">Unauthorized</p>
<p>The request requires user authentication</p>
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>

Anything jump out to you?

Really appreciate it!

Matt

sdagley
Esteemed Contributor II

@mbezzo Are you sure the account you're using for the API call has Read and Update permissions for Static Computer Groups? It also looks like you're using Jamf Cloud, so you might ask Jamf support if there are additional steps required for accessing the API with it.

mbezzo
Contributor III

Yup - just verified again and the account has full access to everything. I'm using this same account for another API script I've made and that one is working just fine... So, something is weird here! Now to just find it lol.

Thanks for the help!

sdagley
Esteemed Contributor II

@mbezzo Just noticed your snippet shows ‘theActualAPIuser:theActualAPIpassword’ ("smart" quotes rather than '). That's going to be a problem as bash doesn't consider them to be the same thing. There may also be a problem with having the username:password inside a single quoted glob if that's actually how you constructed your curl command as it's going to be parsed as a single argument rather than 2 arguments separated by a : so follow the format in the sample I posted.

mbezzo
Contributor III

AHHHHHHHH I hate everything. Created new API account - boom. Works perfectly. NO IDEA why my existing account is failing for this specific use case... Anyway, @sdagley - your script is a perfect use for this. I put a "sleep 10" between adding the computer to the group and removing it - watched my test device as the config profile was removed, and seconds later reappeared issuing a new cert. FANFRICKINTASTIC. :)

So, in summary: Create a new (empty) static computer group and scope it to be excluded from the config profile in question.
Create Self Service item that runs @sdagley's script above, configuring it so it adds the computer to your newly created static group. Add a short sleep in the script after adding to the group before removing from the group.

And there you have a quick and easy way to remove/redeploy a config profile!

Thanks everyone!

mbezzo
Contributor III

okay, I'm fancying up (that's a word, right?) this script with some pop up notifications, but I'm finding that the curl connection is remaining open so it's passing my recon data and prompt data through curl, which means everything is failing again. Anybody have ideas on how to end the curl session after it's done the work?

sdagley
Esteemed Contributor II

@mbezzo Try removing the standalone="no" from the xmlHeader line in the script so that it reads xmlHeader="<?xml version="1.0" encoding="UTF-8"?>". It's an artifact of the script I borrowed to break out the components of the curl call and I don't believe it's necessary.

mbezzo
Contributor III

Awesome, thanks - will give that a shot!

mbezzo
Contributor III

That seems to have done the trick!