Introduction
At the time of writing this article, the JF CLI does not support Cargo. As such, setting up a Cargo project in GitHub Actions can prove challenging. In this guide, I will explain how to set up a GitHub Actions pipeline to utilize Artifactory's functionalities with Cargo while authenticating with OIDC.
Adding The Registry:
First, you will want to set up the config.toml file to allow the Cargo client in the GitHub runner to reach your Artifactory instance:
steps:
- name: add registry
run: |
touch /home/runner/.cargo/config.toml
echo "[registry]" >> /home/runner/.cargo/config.toml
echo "default = \"artifactory\"" >> /home/runner/.cargo/config.toml
echo "global-credential-providers = [\"cargo:token\"]" >> /home/runner/.cargo/config.toml
echo "[registries.artifactory]" >> /home/runner/.cargo/config.toml
echo "index = \"sparse+${{ secrets.ART_URL }}/artifactory/api/cargo/${{ vars.REPO }}/index/\"" >> /home/runner/.cargo/config.toml
echo "[source.artifactory-remote]" >> /home/runner/.cargo/config.toml
echo "registry = \"sparse+${{ secrets.ART_URL }}/artifactory/api/cargo/${{ vars.REPO }}/index/\"" >> /home/runner/.cargo/config.toml
echo "[source.crates-io]" >> /home/runner/.cargo/config.toml
echo "replace-with = \"artifactory-remote\"" >> /home/runner/.cargo/config.toml
Here we create the config.toml file to be used by the client, let's dissect some important arguments used:
- echo "default = \"artifactory\"" >> /home/runner/.cargo/config.toml Here we set Cargo's default registry to Artifactory. This is done to avoid having to use "--registry=artifactory" on every request.
- echo "global-credential-providers = [\"cargo:token\"]" >> /home/runner/.cargo/config.toml Now we specify that we will use a token to authenticate.
- echo "index = \"sparse+${{ secrets.ART_URL }}/artifactory/api/cargo/${{ vars.REPO }}/index/\"" >> /home/runner/.cargo/config.toml Importantly, here we set the index and later the registry address. The secrets.ART_URL and vars.REPO variables are the first few we will need to set in our project, ART_URL being the URL for your instance in the form of "https://artifactory.com", and REPO being the name of the repository to be used for resolution.
- The rest keeps setting up the client in a similar fashion, so we'll move on.
Authentication:
Now we will need to set up authentication. In this guide, we will use the OIDC integration to fetch an access token. If you'd like to just use a static access token, you could use only the "add token" step, passing the access token as a secret instead of the environment variable.
- name: Get id token
run: |
ID_TOKEN=$(curl -sLS -H "User-Agent: actions/oidc-client" -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq .value | tr -d '"')
echo "ID_TOKEN=${ID_TOKEN}" >> $GITHUB_ENV
- name: Exchange token with access
env:
ID_TOKEN: ${{ env.ID_TOKEN }}
JFROG_PLATFORM_URL: ${{ secrets.ART_URL }}
run: |
ACCESS_TOKEN=$(curl -XPOST -H "Content-Type: application/json" "${{ secrets.ART_URL }}/access/api/v1/oidc/token" -d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", \"subject_token\": \"${ID_TOKEN}\", \"provider_name\": \"${{ secrets.PROVIDER_NAME }}\"}" | jq .access_token | tr -d '"')
echo "ACCESS_TOKEN=${ACCESS_TOKEN}" >> $GITHUB_ENV
echo "::add-mask::$ACCESS_TOKEN"
- name: add token
run: |
touch /home/runner/.cargo/credentials.toml
echo "[registries.artifactory]" >> /home/runner/.cargo/credentials.toml
echo " token=\"Bearer ${{ env.ACCESS_TOKEN }}\"" >> /home/runner/.cargo/credentials.toml Now we're looking at setting up the credentials.toml file, here is the breakdown:
- First, getting the ID token from the OIDC provider:
- name: Get id token
run: |
ID_TOKEN=$(curl -sLS -H "User-Agent: actions/oidc-client" -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq .value | tr -d '"')
echo "ID_TOKEN=${ID_TOKEN}" >> $GITHUB_ENV
Here, we request the ID token from GitHub and store it in the GitHub environment.
- This token is then sent to Artifactory to be exchanged for an Access Token:
- name: Exchange token with access
env:
ID_TOKEN: ${{ env.ID_TOKEN }}
JFROG_PLATFORM_URL: ${{ secrets.ART_URL }}
run: |
ACCESS_TOKEN=$(curl -XPOST -H "Content-Type: application/json" "${{ secrets.ART_URL }}/access/api/v1/oidc/token" -d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", \"subject_token\": \"${ID_TOKEN}\", \"provider_name\": \"${{ secrets.PROVIDER_NAME }}\"}" | jq .access_token | tr -d '"')
echo "ACCESS_TOKEN=${ACCESS_TOKEN}" >> $GITHUB_ENV
echo "::add-mask::$ACCESS_TOKEN"
Now that we have the ID token from GitHub we can use it in a request to the /access/api/v1/oidc/token API endpoint to exchange it with an access token. To perform this request we use another previously declared secret "PROVIDER_NAME", this will be the name of the provider defined in Artifactory, more information about that here.
After storing the token in the GitHub environment we run "echo "::add-mask::$ACCESS_TOKEN"" to hide the content of the token in the console.
- Finally, we add the newly created token to the credentials.toml file to be used by the Cargo client:
- name: add token
run: |
touch /home/runner/.cargo/credentials.toml
echo "[registries.artifactory]" >> /home/runner/.cargo/credentials.toml
echo " token=\"Bearer ${{ env.ACCESS_TOKEN }}\"" >> /home/runner/.cargo/credentials.toml
Once this is done we're all set to use our Artifactory instance as a Cargo registry.
Putting it together:
Having it all together an example Workflow file would look like this:
name: Rust Exemple
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
steps:
- name: add registry
run: |
touch /home/runner/.cargo/config.toml
echo "[registry]" >> /home/runner/.cargo/config.toml
echo "default = \"artifactory\"" >> /home/runner/.cargo/config.toml
echo "global-credential-providers = [\"cargo:token\"]" >> /home/runner/.cargo/config.toml
echo "[registries.artifactory]" >> /home/runner/.cargo/config.toml
echo "index = \"sparse+${{ secrets.ART_URL }}/artifactory/api/cargo/${{ vars.REPO }}/index/\"" >> /home/runner/.cargo/config.toml
echo "[source.artifactory-remote]" >> /home/runner/.cargo/config.toml
echo "registry = \"sparse+${{ secrets.ART_URL }}/artifactory/api/cargo/${{ vars.REPO }}/index/\"" >> /home/runner/.cargo/config.toml
echo "[source.crates-io]" >> /home/runner/.cargo/config.toml
echo "replace-with = \"artifactory-remote\"" >> /home/runner/.cargo/config.toml
- name: Get id token
run: |
ID_TOKEN=$(curl -sLS -H "User-Agent: actions/oidc-client" -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq .value | tr -d '"')
echo "ID_TOKEN=${ID_TOKEN}" >> $GITHUB_ENV
- name: Exchange token with access
env:
ID_TOKEN: ${{ env.ID_TOKEN }}
JFROG_PLATFORM_URL: ${{ secrets.ART_URL }}
run: |
ACCESS_TOKEN=$(curl -XPOST -H "Content-Type: application/json" "${{ secrets.ART_URL }}/access/api/v1/oidc/token" -d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", \"subject_token\": \"${ID_TOKEN}\", \"provider_name\": \"${{ secrets.PROVIDER_NAME }}\"}" | jq .access_token | tr -d '"')
echo "ACCESS_TOKEN=${ACCESS_TOKEN}" >> $GITHUB_ENV
echo "::add-mask::$ACCESS_TOKEN"
- name: add token
run: |
touch /home/runner/.cargo/credentials.toml
echo "[registries.artifactory]" >> /home/runner/.cargo/credentials.toml
echo " token=\"Bearer ${{ env.ACCESS_TOKEN }}\"" >> /home/runner/.cargo/credentials.toml
- name: Build
run: cargo install helloworld --verbose
Now you are ready to use Artifactory in your Rust GitHub Actions Pipelines, just replace the "Build" step and use Cargo as you usually would.