Keeping Secrets Safer in AutoPkg CI Pipelines

Published January 13, 2023 / 703 words / ~4 minutes to read

Since joining the Gusto CPE team this past year, I’ve had the opportunity to work directly on the increasingly popular GitHub Actions AutoPkg runner. Gusto open sourced this back in 2020 and has since caught on in other organizations looking to move away from managing physical Mac infrastructure (avoiding the load bearing Mac mini) to ephemeral AutoPkg runs utilizing CI in the cloud. GitHub Actions is a natural choice for teams already using GitHub, and because it is one of the few CI solutions which offers Mac runners out of the box. Recently, while working on a recipe for Okta Verify, I needed a way to include an API password in an input variable without including it directly in a recipe override. In the past, when working with static Mac hardware, I would add secrets to recipe overrides and keep them locally only on the Mac used to run AutoPkg. While still mostly insecure, at least those secrets weren’t also available in a code repo, and less prone to being compromised. With ephemeral CI runs though, this isn’t possible. A secret store which can be referenced at runtime, outside of the repo becomes necessary. Thankfull, my colleagues at Gusto had encountered this before and already had a solution to avoid committing plain text secrets.

GitHub Actions Secrets

GitHub Actions, like many CI products, provides its own encrypted secret store. Azure Pipelines has secret variables, CircleCI has contexts, or you use an external source like HashiCorp Vault. Read more about GitHub’s solution in the docs here. This is where to put API keys, tokens, passwords, and any other sensitive data which shouldn’t be exposed. Secrets do not need to be of a certain naming scheme, but should be descriptive enough to easily reference in a workflow later. For example, the Okta Verify download recipe requires Okta organization ID (OKTA_ORG_ID), API username (OKTA_USERNAME), and password (OKTA_PASSWORD), all of which to varying degrees could be considered secrets.

GitHub Actions secret store

AutoPkg Environment Variables

Enter a little known feature where system environment variables starting with AUTOPKG_ are included in AutoPkg runs. All that’s required is those environment variables be named like AUTOPKG_OKTA_ORG_ID.

2140
2141
2142
2143
2144
2145
2146
2147
# Add variables from environment
cli_values = {}
for key, value in list(os.environ.items()):
    if key.startswith("AUTOPKG_"):
        if options.verbose > 1:
            log(f"Using environment var {key}={value}")
        local_key = key[8:]
        cli_values[local_key] = value

Take a look at Gusto’s open source GitHub Actions workflow and you’ll see the Run AutoPkg step already has an env defined. Most likely other CI solutions have similar support in their workflow syntax. Add lines for whatever secrets are required in your recipe, ensuring the values match the associated input variables exactly.

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
- name: Run AutoPkg
      run: |
        python3 autopkg_tools.py -l recipe_list.json
        if [ -f pull_request_title ]; then
        echo "TITLE=$(cat pull_request_title)" >> $GITHUB_ENV
        echo "BODY<<EOF" >> $GITHUB_ENV
        cat pull_request_body >> $GITHUB_ENV
        echo "EOF" >> $GITHUB_ENV
        fi        
      env:
        RECIPE: ${{ github.event.inputs.recipe }}
        SLACK_WEBHOOK_TOKEN: ${{ secrets.SLACK_WEBHOOK_URL }}
        AUTOPKG_OKTA_ORG_ID: ${{ secrets.OKTA_ORG_ID }}
        AUTOPKG_OKTA_USERNAME: ${{ secrets.OKTA_USERNAME }}
        AUTOPKG_OKTA_PASSWORD: ${{ secrets.OKTA_PASSWORD }}

Overrides and Secrets

Continuing on with Okta Verify as an example, run autopkg make-override OktaVerify.download to create a recipe override. The resulting file will have empty Okta specific input variables for OKTA_ORG_ID, OKTA_PASSWORD, and OKTA_USERNAME as they are meant to be user supplied. Keep them empty. Since these variables were already defined in the workflow file earlier, and start with AUTOPKG_, they will be included at runtime. Below is a truncated example.

<?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>Identifier</key>
	<string>local.download.OktaVerify</string>
	<key>Input</key>
	<dict>
		<key>NAME</key>
		<string>OktaVerify</string>
		<key>OKTA_ORG_ID</key>
		<string></string>
		<key>OKTA_PASSWORD</key>
		<string></string>
		<key>OKTA_USERNAME</key>
		<string></string>
	</dict>
</dict>
</plist>

Now when the recipe is run through GitHub Actions, even with input variables not defined in the override, those values will be read from the runner environment. Plain text secrets successfully avoided. Keep in mind secrets included in CI runs, and AutoPkg in general, still aren’t entirely safe. While this method protects secrets from prying eyes and unauthorized repo access, AutoPkg itself does nothing to secure variables. Depending on verbosity level, log output could include these values. Be sure to take this into account when setting up your CI.