swift-package-manager: Fails to save credentials to keychain when using login command with token option

Description

Hi,

When attempting to execute the package-registry login command using the token option, the credentials fail to save to the keychain resulting in a -25308 error.

Since Xcode only supports registry credentials saved in Keychain, using netrc to authorize does not work either (as mentioned in this issue discussion).

This makes it impossible to do things like xcodebuild -resolvePackageDependencies in a CI pipeline. Please note suggested solutions like this one and this one assume access to the Keychain UI which is not available in a CI pipeline/context (e.g., Creating a build using Xcode Cloud).

Thank you in advance for your help!

Expected behavior

The credentials should be persisted in the keychain without error.

Actual behavior

When executing the command mentioned, the following error occurs:

Failed to save credentials for \‘https://my_domain.d.codeartifact.the_region.amazonaws.com/[](https://my_domain.d.codeartifact.the_region.amazonaws.com/)’ to keychain: status -25308”

Steps to reproduce

CI Pipeline example 1 - Amazon EC2 running macOS 13.5.2, Xcode 15.0:

(Assumes AWS CLI is installed and configured)

  1. Get authorization token
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain my_domain --domain-owner my_domain_owner_id --region my_region --query authorizationToken --output text --profile my_profile`
  1. Set registry
swift package-registry set --global https://my_domain.d.codeartifact.my_region.amazonaws.com/swift/my_repo/
  1. Log in to registry
swift package-registry login https://my_domain.d.codeartifact.my_region.amazonaws.com/swift/my_repo/login --token ${CODEARTIFACT_AUTH_TOKEN}
  1. Login is successful (but credentials are not saved to keychain)

CI Pipeline example 2 - Xcode Cloud running macOS 13.5.2, Xcode 15.1:

(occurs post clone of repo as part of ci_post_clone.sh script)

  1. Install AWS CLI and configure
  2. Get authorization token
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain my_domain --domain-owner my_domain_owner_id --region my_region --query authorizationToken --output text --profile my_profile`
  1. Set registry
swift package-registry set --global https://my_domain.d.codeartifact.my_region.amazonaws.com/swift/my_repo/
  1. Log in to registry
swift package-registry login https://my_domain.d.codeartifact.my_region.amazonaws.com/swift/my_repo/login --token ${CODEARTIFACT_AUTH_TOKEN}
  1. Login is successful (but credentials are not saved to keychain)

Swift Package Manager version/commit hash

Swift Package Manager - Swift 5.9.0

Swift & OS version (output of swift --version ; uname -a)

CI Pipeline example 1 - Amazon EC2 running macOS 13.5.2, Xcode 15.0:

swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx13.0
Darwin github-runner-macos-02 22.6.0 Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:52 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T8103 arm64

CI Pipeline example 2 - Xcode Cloud running macOS 13.5.2, Xcode 15.1:

swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: x86_64-apple-macosx13.0
Darwin e1b43c42-7e4f-47a9-9e31-f99a59f26be5-f46ffd7c-f6l9d-vm.podset-vms.dt-skywagon-workers-prod-vm.svc.kube.us-west-3f.k8s.cloud.apple.com 22.6.0 Darwin Kernel Version 22.6.0: Wed Jul  5 22:21:56 PDT 2023; root:xnu-8796.141.3~6/RELEASE_X86_64 x86_64

About this issue

  • Original URL
  • State: open
  • Created 6 months ago
  • Comments: 42 (4 by maintainers)

Most upvoted comments

@sebsto – I actually was able to get it working via security with a minor tweak 🎉

TL;DR, instead of just including /usr/bin/xcodebuild as a preapproved application (using -T) when re-adding the password (via security add-internet-password), I saw that my CI machine (github actions) was using a different xcodebuild (Applications/Xcode_15.0.1.app/Contents/Developer/usr/bin/xcodebuild), so I included that also.

  1. Create and unlock keychain
security create-keychain -p "${MATCH_KEYCHAIN_PASSWORD}" "${MATCH_KEYCHAIN_NAME}"
security default-keychain -s "${MATCH_KEYCHAIN_NAME}"
security unlock-keychain -p "${MATCH_KEYCHAIN_PASSWORD}" "${MATCH_KEYCHAIN_NAME}"
security set-keychain-settings "${MATCH_KEYCHAIN_NAME}"
  1. Add new keychain to existing keychain list
EXISTING_KEYCHAINS=( $( security list-keychains | sed -e 's/ *//' | tr '\n' ' ' | tr -d '"') )
sudo security list-keychains -s "${MATCH_KEYCHAIN_NAME}" "${EXISTING_KEYCHAINS[@]}"
security list-keychain
  1. Log into code artifact with the proper authorizations
export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain $DOMAIN --domain-owner $DOMAIN_OWNER --query authorizationToken --region $REGION --output text`
swift package-registry login "$SEVER/login" --token $CODEARTIFACT_AUTH_TOKEN
  1. Delete whatever password was inserted into the keychain by the login command because that password doesn’t have the proper preapproved application list and thus would prompt the Keychain password UI dialog causing the build system to hang.
security delete-internet-password -a token -s $SERVER -r htps "${MATCH_KEYCHAIN_NAME}"
  1. Add the same password back with the proper approved application list. The main emphasis here is that instead of just including /usr/bin/xcodebuild, I saw that my CI machine (github actions) was using a different xcodebuild (Applications/Xcode_15.0.1.app/Contents/Developer/usr/bin/xcodebuild), so I included that also
export PREAPPROVED_APPLICATION_LIST=(-T /usr/bin/security -T /usr/bin/codesign -T /usr/bin/productbuild -T /usr/bin/productsign -T /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-package -T /Applications/Xcode_15.0.1.app/Contents/Developer/usr/bin/xcodebuild -T /Applications/Xcode.app)
security add-internet-password -a token -s $SERVER -w $CODEARTIFACT_AUTH_TOKEN -r htps -U "${PREAPPROVED_APPLICATION_LIST[@]}" $HOME/Library/Keychains/"${MATCH_KEYCHAIN_NAME}"
security set-internet-password-partition-list -a token -s $SERVER -S "com.apple.swift-package,com.apple.security,com.apple.dt.Xcode,apple-tool:,apple:,codesign" -k "${MATCH_KEYCHAIN_PASSWORD}" "${MATCH_KEYCHAIN_NAME}"
security find-internet-password -s $SERVER

@sebsto tried your approach and it worked! Thank you both again (@toheebster) for your efforts!

My only concern is my lack of understanding about all the unrelated items in the login keychain that get deleted (certs, app passwords, etc.). Wondering if I should/can export/move those from/to the keychain I’m creating.

I still need to test this approach in the CI pipeline using Xcode Cloud servers. Will follow up about that shortly.

Thank @toheebster - you made it - I confirm this works.

The main two differences with what I tried

  • you delete the item created by SwiftPM first. If you don’t a second item is created in the keychain, despite the -U (update) flag. This might cause confusion to application consuming the token as we don’t know which item they picked up.

  • The security set-internet-password-partition-list is required. I should have known. I use that to import signing keys.

I just tested end to end with a slightly different script, but this is a matter of personal taste 😃

Prepare the keychain

KEYCHAIN_PASSWORD=$(openssl rand -base64 20)
KEYCHAIN_NAME=login.keychain
SYSTEM_KEYCHAIN=/Library/Keychains/System.keychain

if [ -f $HOME/Library/Keychains/"${KEYCHAIN_NAME}"-db ]; then
    echo "Deleting old ${KEYCHAIN_NAME} keychain"
    security delete-keychain "${KEYCHAIN_NAME}"
fi
echo "Create Keychain"
security create-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_NAME}"

EXISTING_KEYCHAINS=( $( security list-keychains | sed -e 's/ *//' | tr '\n' ' ' | tr -d '"') )
sudo security list-keychains -s "${KEYCHAIN_NAME}" "${EXISTING_KEYCHAINS[@]}"

echo "New keychain search list :"
security list-keychain 

echo "Configure keychain : remove lock timeout"
security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_NAME}"
security set-keychain-settings "${KEYCHAIN_NAME}"

Get CodeArtifact token and repo

export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token \
                                     --region us-west-2 \
                                     --domain YOUR_DOMAIN   \
                                     --domain-owner YOUR_ACCOUNT   \
                                     --query authorizationToken  \
                                     --output text`

export CODEARTIFACT_REPO=`aws codeartifact get-repository-endpoint  \
                                --region us-west-2   \
                                --domain YOUR_DOMAIN   \
                                --domain-owner YOUR_ACCOUNT    \
                                --format swift     \
                                --repository YOUR_REPO    \
                                --query repositoryEndpoint   \
                                --output text`        

Login (this creates the entry in the keychain

aws codeartifact login   \
--region us-west-2    \
--tool swift   \
--domain YOUR_DOMAIN  \
--repository YOUR_REPO  \
--namespace aws \
--domain-owner YOUR_ACCOUNT 

# verify the entry was created 
security find-internet-password   "${KEYCHAIN_NAME}"

Delete and recreate the entry, as @toheebster said 😃

SERVER=$(echo $CODEARTIFACT_REPO | sed  's/https:\/\///g' | sed 's/.com.*$/.com/g')
AUTHORISATION=(-T /usr/bin/security -T /usr/bin/codesign -T /usr/bin/xcodebuild -T /usr/bin/swift -T /Applications/Xcode-15.2.app/Contents/Developer/usr/bin/xcodebuild)

security delete-internet-password -a token -s $SERVER -r htps "${KEYCHAIN_NAME}"

security add-internet-password -a token \
                               -s $SERVER \
                               -w $CODEARTIFACT_AUTH_TOKEN \
                               -r htps \
                               -U \
                               "${AUTHORISATION[@]}" \
                               "${KEYCHAIN_NAME}"

security set-internet-password-partition-list \
             -a token \
             -s $SERVER \
             -S "com.apple.swift-package,com.apple.security,com.apple.dt.Xcode,apple-tool:,apple:,codesign" \
             -k "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_NAME}"

security find-internet-password   "${KEYCHAIN_NAME}"

@Joel-Bell-okcupid now that we have one workaround (and two sets of script for your convenience) - can you test on your side and report back ?

@toheebster I told you I spent 4 hours last night on that. Of course, I verified. Using the security find-internet-password -w option on a laptop + using the diff command + copying over the working keychain from my machine to EC2 Mac … all failed

You’re redoing all my thought process from last night. Please try it yourself. I gave up after 4 hours. I will try the programmatic approach in SwiftPM source code instead.