# GitHub Actions and Private Submodules: Fine-Grained Token Setup
Or, "why the heck are simple things hard"?
My organisation has a private repo, let's call it "shared". This contains shared code like protobuf definitions, API clients, common types, and similar things. It doesn't make sense for them to sit in a singular repo so it has been broken out into its own.
Deploying the "main" repos requires cloning this shared repo as a submodule. This was much more difficult than it should've been, so I wrote up this blog post so future me won't be so frustrated trying to figure it out.
## The Old Way: Personal Access Tokens
Nice and simple, but they expire quite quickly, are tied to the user and usually have too much permissions.
```yaml
jobs:
deploy:
# needs: wait-on-checks
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
token: ${{ secrets.GH_PAT }}
```
## The New Way: Fine-Grained Tokens
It's much more difficult than you think!
The permissions are a lot more precise (obviously). The token also lives on the organisation, so you don't have to tie company processes to your GitHub account.
### Token Setup
Grant the token access on the specific submodule you want, and permit it "content read" and "metadata" scopes.
### Workflow Changes
Change your CI workflow:
- First, clone the repo without submodules
- Then, replace your SHARED_TOKEN as the global git user
- Next, replace SSH git module URL schemes with HTTPS
```yaml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: true
submodules: false
- name: Configure Git for submodule access
run: |
# Configure Git to use token for GitHub URLs - do this FIRST
git config --global url."https://${{ secrets.SHARED_TOKEN }}@github.com/".insteadOf "https://github.com/"
# Update submodule URL from SSH to HTTPS
git config --file=.gitmodules submodule.v3-shared.url https://github.com/nii236/shared.git
# Sync the changes
git submodule sync --recursive
- name: Checkout submodules
run: |
# Now fetch the submodules
git -c protocol.version=2 submodule update --init --force --depth=1 --recursive
- name: Remove Git token configuration
if: always()
run: |
git config --global --unset url."https://${{ secrets.SHARED_TOKEN }}@github.com/".insteadOf
```
The key thing is doing the URL rewriting before trying to update the submodules. The cleanup step at the end removes the global git config so you don't leak tokens.
## Conclusion
Fine-grained tokens are more secure and better for organizations, but the setup is way more involved than it should be. Once you get this pattern working though, you can copy it to any repo that needs private submodules.