Macjutsu / super

S.U.P.E.R.M.A.N. optimizes the macOS software update experience.
Apache License 2.0
589 stars 78 forks source link

Feature Request: Jamf Pro 10.46.0 provides API access to management account name and password #86

Open wakco opened 1 year ago

wakco commented 1 year ago

Change in Jamf Pro 10.46.0 … Local Administrator Password Solution (LAPS) provides API access to look up an existing account and password.

Obviously we would still need to make sure the account has a Secure Token, but that is relatively easy to do, this would enable super to collect a known changing password and use that to trigger softwareupdate.

Macjutsu commented 1 year ago

Not saying no... but this would significantly increase the potential attack surface for whatever Jamf API account is stored by super in the System Keychain. There is potential to gain an admin password for not just the local computer... but for every computer in an organization.

wakco commented 1 year ago

Thankfully admin access is required to look at that keychain entry, but I agree, that said, the security issues are something Jamf will need to consider regarding their API.

I had written something else, but you have given me an idea for a decent feature request for Jamf, for the jamf command, be able to do something like jamf adminLogin softwareupdate --install --all --restart, and then the jamf command could on Apple Silicon Macs, automatically add or pass the admin login credentials to softwareupdate, and perhaps the same for sysadminctl as well as any other command we could have it handle. Perhaps adding something from the policy to allow a script to use the command (for sysadminctl).

Jamf "Idea" JN-I-27135

Macjutsu commented 1 year ago

Digging into this new feature... there are a lot of dependencies and several outstanding issues that make it unsuitable for deployment at this time. I will reevaluate as Jamf makes improvements.

jelockwood commented 1 year ago

@wakco I have been trying on Apple Silicon Macs to get the MDM workflow for upgrades to work, so far unsuccessfully and I am willing to believe this is mainly down to Apple. ☹️

As such I am now considering the possibility of using a local admin account like you appear to be. I have recently joined an organisation and currently they have the same credentials on all Macs which is on my list of things to fix, i.e. to implement LAPS. I have had a brief look at Jamf's new LAPS feature but I get the impression it is not adequate yet to the purpose. I have previously used a script which stores the LAPS password in an extension attribute.

It looks like it might be possible to use the value of this extension attribute as a parameter to a policy script parameter. If so I could then run the Super policy with parameters which pass the LAPS user name and password to Super during deployment and allow super to then create a local account.

According to the Jamf docs, this is done by using a Jamf variable in the script parameter so it might looks something like the following -

--admin-password=$EXTENSIONATTRIBUTE_5

Note: The 5 refers to the number of the extension attribute so will be dependent on your setup.

I have a string suspicion however that the eventually (hopefully) created local credential will lack the required volume owner etc. permissions as Apple for example make it difficult to script giving a secure token to a script created user account. (Another Apple issue.)

Macjutsu commented 1 year ago

You can not set the value of a script parameter via that mechanism. It's only designed for Configuration Profiles.

jelockwood commented 1 year ago

@Macjutsu The $EXTENSIONATTRIBUTE was to read the LAPS password for the Mac that Jamf was running the policy and script on, not to set it. It would be set by the LAPS script using the Jamf API.

The idea was to read the LAPS password from the extension attribute and pass it to Super. As mentioned I have previously used a LAPS script which stored the password in plain text in the extension attribute and hence should work as is. However these days it is considered better to store the password in an encrypted form even though you have to be an approved Jamf user to access it.

I have had a look at some other LAPS solutions for Jamf and the following one looks better to me.

macOSLAPS

The author has provided an example script to retrieve and decode the LAPS password. In theory Super could be enhanced to use the $JSSID to look up the relevant extension attribute and decode its contents and then have the correct LAPS password for the Mac in question and hence be able to use it to do the required authentication locally.

LAPS Decode Password Script

The script will need some modifying for this purpose to make it fully automated and hence remove some of the interactive aspects. (Basically strip out everything except reading and decoding the extension attribute.) I could myself build this in to Super but I would have to do this each time you updated it. Ideally Super would be modified to have an additional script parameter e.g.

--use-macOSLAPS and --use-macOSLAPS-off

And accordingly use this or not. It should be possible to put this modified script in the Super folder and call it based on this additional parameter. I could create the modified script for you if you are willing to add it to Super.

Macjutsu commented 1 year ago

I am aware of LAPS solutions... but there are two fundamental security problems with leveraging them in any local process or script like super.

  1. Just because you have a local admin account, doesn't mean it can authenticate a macOS update/upgrade. The account requires Volume Ownership permissions which can not be programmatically given to a new account without first providing the credentials of an existing Volume Owner user. So even the act of creating a LAPS account capable of performing a macOS update/upgrade can not be automated. It's my understanding that the new Jamf LAPS solution will suffer from this same macOS security design choice.
  2. In order to lookup any EA value super must authenticate to the Jamf Pro API... this means that super would be storing that credential in the System Keychain. Because of Jamf Pro's security model, that credential, if discovered, would allow anyone to have full read access to your entire Jamf Pro inventory database. Obviously, this would be a significant security risk.
jelockwood commented 1 year ago

@Macjutsu Yes, I am aware of the volume ownership requirement. I have previously had Jamf run a policy which gets the user to authenticate locally giving an IT admin account a secure token if needed and then previously had a Jamf policy which does the LAPS function. (I am looking at a new way which piggybacks on Jamf Connect to automate creating a local admin account with an initial password and secure token/volume ownership.) So I would be ensuring that the local admin account is up to the job for Super and hence can be used by Super to create the Super service account.

I in fact did use this today to get Super to work for doing the upgrade successfully. I passed the password for the local admin account in a Jamf policy with the Super install script, it used the password to create the Super service account, I had also a Jamf Profile setting for Super to failover (ALWAYS) in the event of an MDMworkflow failure and it then successfully installed the macOS Ventura upgrade.

Super is already setup as per your instructions to authenticate to Jamf API - this is working. It can therefore potentially use the same approach to authenticate reading an extension attribute which is storing a LAPS password. I am using the $JSSID approach recommended by your instructions which supposedly means it cannot read the entire inventory. (Although I would have thought it would be possible to emulate this by looping through all possible JSSID values.)

All the LAPS solutions for Jamf rely on using the API to set and to read the values and I would presume this includes Jamf itself. However the one I am referring to like several others does encrypt the contents. This encryption key could be passed to Super via a script parameter.

Macjutsu commented 1 year ago

Yes... but due to the limitations of Jamf Pro's security model... the permissions required for the Jamf Pro API account used by super to read EAs would also allow it read EVERY SINGLE EA for ALL of your computer records.

And it doesn't matter if you have added the extra (technically not actually more secure "encrypted" strings) method because super would have to store the decryption secrets in the Keychain as well. In other words, you just made it slightly more complicated but no more secure.

In short, because of limitations in Jamf Pro and because super has to store the secrets, if someone can view their local System Keychain (default for admin), then they can know all the LAPS passwords for your entire environment.

jelockwood commented 1 year ago

@Macjutsu I see your point about Super directly reading EA fields.

However I think if we used as a script parameter something like -

--admin-password=$EXTENSIONATTRIBUTE_5

This would not require giving the SuperApi credentials full EA access and the content of the designated EA would be passed to your script. A Jamf admin would like all the other script parameters specifically set this up so it cannot be auto scraped and it would also be for a specific script i.e. Super. Furthermore the content of the EA in this case would be in encrypted form and would need decrypting in your script to utilise. This likely will require using two script parameters one for the encryption key and one for the encrypted admin password as above. Basically the same way that the macOSLAPS script works except your script would not have full EA access.

Macjutsu commented 1 year ago

This is NOT something Jamf Pro can do... As I have already stated earlier....

"You can not set the value of a script parameter via that mechanism. It's only designed for Configuration Profiles."

Again... sending the value of Jamf Pro attributes is only possible with CONFIGURATION PROFILES.

jelockwood commented 1 year ago

@Macjutsu Ok thanks for the correction. Whilst technically it would be possible to send the encrypted password to the Mac as part of a profile this is not going to be desirable even though it is encrypted.

I will consider writing my own script to retrieve the encrypted password - via the Jamf API, decode it and call super with command line parameters to allow it to create the service account.

Macjutsu commented 1 year ago

Another issue with the configuration profile is that Jamf Pro does not send new configuration profiles when the value of a $VARIABLE changes. So when you cycle the LAPS password you would have to push a new configuration profile manually or with some other external automation.

This last point would be the best approach, as long as you don't store any data on the local drive while the pre-script is running.

iDrewbs commented 1 year ago

What I ended up doing is use the same component that populates the LAPS Extension Attribute in a script to pass local admin creds to super to create the super service account. If for some reason LAPS hasn't been setup on a particular Mac, it pivots to deploying super using the MDM workflow. It's been working out well for me so far.

jelockwood commented 1 year ago

@Macjutsu @wakco @iDrewbs

Ok, attached is a script that is designed to sit between Jamf Pro and Super. I have as will be obvious when you view it based it on the Super script itself. Here is the main logic.

  1. This modified script reads the command line parameters passed to it exactly the same way as Super normally does with two important differences, if the value of --admin-acount starts with 'lapssecret-' then it is presumed to contain the name of an extension attribute that will contain the LAPS password, if an additional new command line parameter of --admin-crypt-key is defined then this a) indicates that the admin-password is encrypted and b) if the contents of --admin-crypt-key starts with 'lapscryptkey-' then this indicates the extension attribute containing the decryption key required to decrypt the admin-password value, if both are pointing to extension attributes then the encrypted value is retrieved and then the decryption key is retrieved and it is used to decrypt the admin-password
  2. Once as needed the value(s) are retrieved and any needed decryption performed a new set of command parameters is generated with the decrypted admin-password now used instead and these are passed to the 'real' Super script
  3. Instead of the Jamf Policy running the standard Super script, you would set the policy up in Jamf Pro exactly the same but use this script instead

This will allow all the following possible scenarios to work.

  1. As now the same admin password for all devices, just have a password in --admin-password=xxx and do not use --admin-crypt-key==xxx
  2. Have the same encrypted password for all devices, put the encrypted value in --admin-password=zzz and the same decryption key in --admin-crypt-key=zzz
  3. Have a unique unencrypted password for each device, put the unique password in an extension attribute and use --admin-password=lapssecret-zzz to point to the extension attribute
  4. Have a unique encrypted password for each device but use the same decryption key for all of them, use --admin-password=lapssecret-zzz to point to the extension attribute and define the decryption key via --admin-crypt-key=xxx
  5. Have a unique encrypted password for each device with unique decryption keys for all of them, use --admin-password=lapssecret-zzz to point to the extension attribute and --admin-crypt-key=lapscryptkey-zzz to point to the extension attribute containing each decryption key

This should cover all the following LAPS solutions and possibly others

  1. https://github.com/NU-ITS/LAPSforMac uses unencrypted LAPS stored in an extension attribute, this LAPS solution seems to be far the oldest and no longer supported, I have previously used it and did myself modify it to cope with the different xpath behaviour newer macOS versions require
  2. https://github.com/joshua-d-miller/macOSLAPS uses unencrypted LAPS stored in an extension attribute BUT either you need to modify the standard extension attribute script or you need to modify this script to strip off the prefix of '| Password: ' and the suffix of ' |'
  3. https://github.com/PezzaD84/macOSLAPS uses an encrypted LAPS stored in an extension attribute with the decryption key also stored in an extension attribute, I am looking at using this solution this time

I have not yet looked closely at it, but I am led to believe that the new Jamf Pro built-in LAPS does not use the existing password to authorise changing to a new password and hence is doing a password reset each time, this would therefore result in destroying the secure token of the LAPS account and makes it unsuitable for my own purposes.

Note: I am sure that my initial script provided here could firstly have a lot more lines removed as un-needed in this case, remember it ends up calling the real, full super script. However as it is the inclusion of such lines may make it easier to see how these changes could if desired be built-in to the Super script itself. Kevin may or may not want to consider that but the reason I did not would be that each time a new version of Super was released the same changes would need to be reapplied. I also suspect that Kevin being far more familiar with his original code may see ways this script could be done more elegantly.

Note to @Macjutsu I am using the $JSSID to retrieve the extension attributes and hence minimising the API permissions needed

super-install.txt

Macjutsu commented 1 year ago

This is a good method if your plan is to only give super the admin password long enough to create it's own service account. Further, since it only needs to be run once, keeping it as a separate "installation" script makes sense as you wouldn't need any of this code in super itself.

However, how does using $JSSID limit the permissions for the API account? Perhaps for the execution of this single script it will... but not for the API account itself. If the API account has permissions to read EAs... it can read ALL EAs... even if this particular script only reads a single EA.

In other words, if this script is using the same Jamf Pro API account that is also passed to super, then a local admin could easily access the Jamf Pro API credentials that super stores in the System Keychain and then gain access to all of the data stored in every computer's EA value.

jelockwood commented 1 year ago

I had misunderstood your use of the $JSSID so no, it slightly helps reduce access but not materially.

As it happens, in order to read extension attributes you have to have read access to the computer record(s). The only way I can see to further secure this with respect to your concern would be either -

  1. Use different dedicated API credentials for this purpose which Super itself does not know and hence is not stored anywhere on the Mac, if this is done using additional command line parameters it would increase the likelihood of running out of fields
  2. Use different dedicated API credentials and hard code them in to the script.

It should be noted I went to great effort with my initial script design to provide masses of flexibility for LAPS support but only need a single extra parameter field.

Regarding the keychain storing Super API credentials.

I seem to recall seeing some software products create their own dedicated keychain with their own password to that keychain so only their software can access it.

Macjutsu commented 1 year ago

super could create another keychain, but then where would it store the password for that keychain?

Any "secret" method super could use would be visible to the public due to its open source nature. As such, we literally couldn't hide access to that second keychain.

This is why most processes use the System Keychain... it's the only keychain that doesn't need a password... just admin/root access.

jelockwood commented 1 year ago

@Macjutsu For what it's worth, I found an error in the last few lines of my proposed script.

Where I pass the regenerated command line parameters to the real super script this needs changing to the following to ensure the parameters are handled properly.

curl --silent -o /tmp/super -L -O https://github.com/Macjutsu/super/raw/main/super
chmod +x /tmp/super
array=($commandPARAMS)
mycmd=(/tmp/super "${array[@]}")
"${mycmd[@]}"

I can see that my script now successfully launches the real super script. I am not convinced however that Super is doing anything.

It seems to suggest the next scheduled event is right now except nothing ever happens. Super.log attached below.

Ok first log is when I use my script (with above fix) to run the real super script. I seem to get an error 137 returned but as per the log Super does seem to install and run. It however DOES NOT show any dialogs.

super copy1.log

Second log is when I use a barebones script with the parameters hard coded in to a variable and then call the real super scrip the same way. This does not seem to trigger an error 137 but again no dialogs are shown after.

super copy2.log

On a Mac with a need to do an upgrade IT DID DO IT but with as mentioned no dialogs.

Macjutsu commented 1 year ago

If you post this to your own repository I can link to it in the Wiki.

jelockwood commented 1 year ago

@Macjutsu I can do that, but I would like to resolve the two queries first.

  1. What would error 137 as per signify?
    
    Fri Jun 16 18:18:45 G4-0001257 super[95723]: **** S.U.P.E.R.M.A.N. 3.0 INSTALLATION ****
    Fri Jun 16 18:18:45 G4-0001257 super[95723]: Installation: Copying file: /Library/Management/super/super
    Fri Jun 16 18:18:45 G4-0001257 super[95723]: Installation: Creating search path link: /usr/local/bin/super
    Fri Jun 16 18:18:45 G4-0001257 super[95723]: Installation: Creating LaunchDaemon helper: /Library/Management/super/super-starter
    Fri Jun 16 18:18:45 G4-0001257 super[95723]: Installation: Setting permissions in: /Library/Management/super
    Fri Jun 16 18:18:45 G4-0001257 super[95723]: **** S.U.P.E.R.M.A.N. 3.0 STARTUP ****
    Fri Jun 16 18:18:46 G4-0001257 super[95723]: Startup: Found previous super instance running with PID 95536, killing...

Error running script: return code was 137.


2. Any idea why the dialogs failed to appear but it did do an upgrade. It was a different Mac that did the upgrade so will not be in the above two logs.
Macjutsu commented 1 year ago

This is a Jamf Pro issue: https://community.jamf.com/t5/jamf-pro/issue-running-script-from-policy/m-p/260226

jelockwood commented 1 year ago

@Macjutsu Based on that Jamf article are you advising I add code to my script to call the real Super script via the logged in user? Or to create a new fresh policy with my unaltered script?

Macjutsu commented 1 year ago

From the solution, "I was able to fix this by just creating a new policy, copying everything over and deleting the old policy. Something must have become corrupted or sticky in that old one that was causing all the issues. Go figure, had nothing to do with the script."

Sometimes things in Jamf Pro become corrupted and this results in error 137. It's resolved by simply re-uploading the scripts and creating fresh Policies with the same settings.

jelockwood commented 1 year ago

@Macjutsu Thanks for the help.

I did make a brand new policy, I made a new script entry in Jamf Pro - although I copied and pasted the script itself and tried again, it still gave an error 137. I then made a new policy again, a new script but this time just the portion of my script that calls yours with hard coded $commandPARAMS (see below) - most of the script is building the commandPARAMS.

This stripped down script worked without an error 137 being triggered. :(

Anyway, whilst I myself continue looking in to this, here are two other questions. I am also getting an error as follows.

Mon Jun 19 09:55:39 G4-0001257 super[76778]: Exit: LaunchDaemon com.macjutsu.super.plist is scheduled to start right now.
/Library/LaunchDaemons/com.macjutsu.super.plist: Service is disabled
Bootstrap failed: 119: Service is disabled
Mon Jun 19 09:55:39 G4-0001257 super[76778]: **** S.U.P.E.R.M.A.N. 3.0 EXIT ****

My stripped down script is as follows and whether run locally or via a Jamf Policy event gives the 119 error.

#!/bin/sh

commandPARAMS="--jamf-account=superapi --jamf-password=password --admin-account=ladmin --admin-password=password"

curl --silent -o /tmp/super -L -O https://github.com/Macjutsu/super/raw/main/super
chmod +x /tmp/super

array=($commandPARAMS)

mycmd=(/tmp/super "${array[@]}")

"${mycmd[@]}"

I am also attaching below a copy of the Super.log from a Mac that did successfully run the Super upgrade cycle but did not show any dialogs or prompts. So it 'suddenly' rebooted.

super.log

jelockwood commented 1 year ago

@Macjutsu Regarding the error 137, this appears to have been down to my retaining too much of your script, in particular PID related code, removing that seems to cure the 137 error.

This leave the 119 error, if you can shed some light on what might cause that I will likely need to remove some more parts of the original script to prevent that conflict.

jelockwood commented 1 year ago

@Macjutsu FYI - I have been working with the author of the macOSLAPS solution I am using and he has added the ability to work with existing LAPS accounts all using the same credentials and then take over randomising them. I have been working on a script to then read those details and pass them to your Super script.

I think this is pretty much working, I am now modifying my script to use a different Jamf API account so Jamf calls my script, my script uses the passed API credentials which are not stored and my script passes the normal SuperAPI credentials and the device specific local admin credentials to your Super script. I think this as much as possible prevents global access to credentials since the SuperAPI credentials cannot read the extension attributes and the API credentials which can are not stored on the client Mac.

I have created my own repo for this although I have not yet finished with testing.

https://github.com/jelockwood/Super-Glue

I have noticed you have recently updated the recommend API settings and added more access to Managed Software Update Plans. It could be that this might help my own experience and why I have felt I needed to enable failover to local admin credentials.

As a separate issue, I do get the impression that even after I have successfully installed your Super script, it only seems to run once and not after a successful macOS update/upgrade run again even if a new version is available. As it happens the way I will update the credentials for Super after the macOSLAPS has changed them should get round this by effectively re-installing and starting Super again. Looking at your launchdaemon I see it is set as launch only once so this might explain matters.

How is Super supposed to automatically re-check for new updates? Does it rely on the user regularly rebooting? What happens if a user does an update which does a reboot, the check sees no other updates and then Super 'quits' and the user does not do another reboot for a number of days? It would appear no further checks happen.

Here is the relevant section from the super.log

Tue Jun 20 14:29:57 G4-0001257 super[54683]: Startup: Found that super is installing, restarting with new LaunchDaemon...
Tue Jun 20 14:29:57 G4-0001257 super[54683]: Verbose Mode: Function makeLaunchDaemonRestartNow: LaunchDaemon com.macjutsu.super.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.macjutsu.super</string>
    <key>LaunchOnlyOnce</key>
    <true/>
    <key>AbandonProcessGroup</key>
    <true/>
    <key>UserName</key>
    <string>root</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Library/Management/super/super-starter</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>
Tue Jun 20 14:29:57 G4-0001257 super[54683]: Exit: LaunchDaemon com.macjutsu.super.plist is scheduled to start right now.
Tue Jun 20 14:29:57 G4-0001257 super[54683]: **** S.U.P.E.R.M.A.N. 3.0 EXIT ****

As you can see it has not re-run Super since June 20th and hence has not spotted and installed macOS 13.4.1 which is now available. I have rebooted it today as well.

Macjutsu commented 1 year ago

As a default super does not check again after a successful update/upgrade. However, there is an option for this: https://github.com/Macjutsu/super/wiki/Deferral-Behavior#recheck-deferral-timer

jelockwood commented 1 year ago

@Macjutsu It turns out the way my script was (re)installing Super when updating the local admin credentials passed to Super was resulting in the launchdaemon being unloaded and not reloaded. I modified my script to explicitly handle this and it now reliable re-runs.

I had already defined the deferred-recheck setting with a value so it was not that.

I have a couple more scenarios to test but I feel my script now fully works with the macOSLAPS solution. I am gradually updating my documentation.

Macjutsu commented 8 months ago

Adding the tag of "Help Wanted" to indicate that unless a safer method is found, I won't be working on this.