Skip to content

Commit 3514912

Browse files
authored
Merge pull request #108 from git-for-windows/self-hosted-runners-in-private-repositories
Self hosted runners in private repositories
2 parents c47fca1 + ebc7f08 commit 3514912

File tree

6 files changed

+172
-100
lines changed

6 files changed

+172
-100
lines changed

.github/workflows/azure-login/action.yml

-43
This file was deleted.

.github/workflows/cleanup-self-hosted-runners.yml

+28-8
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,31 @@ on:
77
- cron: "0 */6 * * *"
88
workflow_dispatch:
99

10+
permissions:
11+
id-token: write # required for Azure login via OIDC
12+
1013
# The following secrets are required for this workflow to run:
11-
# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource
12-
# group specifically for self-hosted Actions Runners.
13-
# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \
14-
# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \
15-
# --sdk-auth
14+
# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource
15+
# group specifically for self-hosted Actions Runners, and to add a federated identity
16+
# to authenticate as the currently-running GitHub workflow.
17+
# az identity create --name <managed-identity-name> -g <resource-group>
18+
# az identity federated-credential create \
19+
# --identity-name <managed-identity-name> \
20+
# --resource-group <resource-group> \
21+
# --name github-workflow \
22+
# --issuer https://token.actions.githubusercontent.com \
23+
# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \
24+
# --audiences api://AzureADTokenExchange
25+
# MSYS_NO_PATHCONV=1 \
26+
# az role assignment create \
27+
# --assignee <client-id-of-managed-identity> \
28+
# --scope '/subscriptions/<subscription-id>/resourceGroups/<resource-group>' \
29+
# --role 'Contributor'
30+
# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which
31+
# the Identity lives)
32+
# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated
33+
# (technically, this is not necessary for `az login --service-principal` with a
34+
# managed identity, but `Azure/login` requires it anyway)
1635
# AZURE_RESOURCE_GROUP - Resource group to find the runner(s) in. It's recommended to set up a resource
1736
# group specifically for self-hosted Actions Runners.
1837
jobs:
@@ -22,10 +41,11 @@ jobs:
2241
steps:
2342
- uses: actions/checkout@v4
2443
- name: Azure Login
25-
uses: ./.github/workflows/azure-login
44+
uses: azure/login@v2
2645
with:
27-
credentials: ${{ secrets.AZURE_CREDENTIALS }}
28-
46+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
47+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
48+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
2949
- name: Discover VMs to delete
3050
env:
3151
GH_APP_ID: ${{ secrets.GH_APP_ID }}

.github/workflows/create-azure-self-hosted-runners.yml

+73-18
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,28 @@ on:
2020
required: false
2121
description: Repo to deploy the runner to. Only needed if runner_scope is set to "repo-level" (defaults to current repository)
2222
deallocate_immediately:
23-
type: string
23+
type: choice
24+
options:
25+
- false
26+
- true
2427
required: true
2528
description: Deallocate the runner immediately after creating it (useful for spinning up runners preemptively)
2629
default: "false"
30+
ephemeral:
31+
type: choice
32+
options:
33+
- false
34+
- true
35+
required: true
36+
description: Start the runner in ephemeral mode (i.e. unregister after running one job)
37+
default: "true"
2738

2839
env:
2940
ACTIONS_RUNNER_SCOPE: ${{ github.event.inputs.runner_scope }}
3041
ACTIONS_RUNNER_ORG: "${{ github.event.inputs.runner_org || github.repository_owner }}"
3142
ACTIONS_RUNNER_REPO: "${{ github.event.inputs.runner_repo || github.event.repository.name }}"
3243
DEALLOCATE_IMMEDIATELY: ${{ github.event.inputs.deallocate_immediately }}
33-
# This has to be a public URL that the VM can access after creation
34-
POST_DEPLOYMENT_SCRIPT_URL: https://raw.githubusercontent.com/${{ github.repository }}/${{ github.ref }}/azure-self-hosted-runners/post-deployment-script.ps1
44+
EPHEMERAL_RUNNER: ${{ github.event.inputs.ephemeral }}
3545
# Note that you'll need "p" (arm64 processor) and ideally "d" (local temp disk). The number 4 stands for 4 CPU-cores.
3646
# For a convenient overview of all arm64 VM types, see e.g. https://azureprice.net/?_cpuArchitecture=Arm64
3747
AZURE_VM_TYPE: Standard_D4plds_v5
@@ -41,15 +51,37 @@ env:
4151
AZURE_VM_REGION: westus2
4252
AZURE_VM_IMAGE: win11-24h2-ent
4353

54+
permissions:
55+
id-token: write # required for Azure login via OIDC
56+
contents: read
57+
4458
# The following secrets are required for this workflow to run:
45-
# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource
46-
# group specifically for self-hosted Actions Runners.
47-
# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \
48-
# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \
49-
# --sdk-auth
59+
# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource
60+
# group specifically for self-hosted Actions Runners, and to add a federated identity
61+
# to authenticate as the currently-running GitHub workflow.
62+
# az identity create --name <managed-identity-name> -g <resource-group>
63+
# az identity federated-credential create \
64+
# --identity-name <managed-identity-name> \
65+
# --resource-group <resource-group> \
66+
# --name github-workflow \
67+
# --issuer https://token.actions.githubusercontent.com \
68+
# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \
69+
# --audiences api://AzureADTokenExchange
70+
# MSYS_NO_PATHCONV=1 \
71+
# az role assignment create \
72+
# --assignee <client-id-of-managed-identity> \
73+
# --scope '/subscriptions/<subscription-id>/resourceGroups/<resource-group>' \
74+
# --role 'Contributor'
75+
# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which
76+
# the Identity lives)
77+
# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated
78+
# (technically, this is not necessary for `az login --service-principal` with a
79+
# managed identity, but `Azure/login` requires it anyway)
5080
# AZURE_RESOURCE_GROUP - Resource group to create the runner(s) in
5181
# AZURE_VM_USERNAME - Username of the VM so you can RDP into it
5282
# AZURE_VM_PASSWORD - Password of the VM so you can RDP into it
83+
# GH_APP_ID - The ID of the GitHub App whose credentials are to be used to obtain the runner token
84+
# GH_APP_PRIVATE_KEY - The private key of the GitHub App whose credentials are to be used to obtain the runner token
5385
jobs:
5486
create-runner:
5587
runs-on: ubuntu-latest
@@ -94,14 +126,21 @@ jobs:
94126
# https://github.com/actions/runner/issues/475
95127
- name: Generate Actions Runner token and registration URL
96128
run: |
129+
# We need to URL-encode the user name because it usually is a GitHub App, which means that
130+
# it has the suffix `[bot]`. If un-encoded, this would cause a cURL error "bad range in URL"
131+
# because it would mistake this for an IPv6 address or something like that.
132+
user_pwd="$(jq -n \
133+
--arg user '${{ github.actor }}' \
134+
--arg pwd '${{ secrets.GITHUB_TOKEN }}' \
135+
'$user | @uri + ":" + $pwd')"
97136
case "$ACTIONS_RUNNER_SCOPE" in
98137
"org-level")
99-
ACTIONS_API_URL="https://api.github.com/repos/$ACTIONS_RUNNER_ORG/actions/runners/registration-token"
100-
ACTIONS_RUNNER_REGISTRATION_URL="https://github.com/$ACTIONS_RUNNER_ORG"
138+
ACTIONS_API_URL="https://$user_pwd@api.github.com/repos/$ACTIONS_RUNNER_ORG/actions/runners/registration-token"
139+
ACTIONS_RUNNER_REGISTRATION_URL="https://$user_pwd@github.com/$ACTIONS_RUNNER_ORG"
101140
;;
102141
"repo-level")
103-
ACTIONS_API_URL="https://api.github.com/repos/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO/actions/runners/registration-token"
104-
ACTIONS_RUNNER_REGISTRATION_URL="https://github.com/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO"
142+
ACTIONS_API_URL="https://$user_pwd@api.github.com/repos/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO/actions/runners/registration-token"
143+
ACTIONS_RUNNER_REGISTRATION_URL="https://$user_pwd@github.com/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO"
105144
;;
106145
*)
107146
echo "Unsupported runner scope: $ACTIONS_RUNNER_SCOPE"
@@ -127,16 +166,30 @@ jobs:
127166
ACTIONS_RUNNER_PATH="D:\a"
128167
fi
129168
169+
# Zip up and Base64-encode the post-deployment script; We used to provide a public URL
170+
# for that script instead, but that does not work in private repositories (and we could
171+
# not even use the `GITHUB_TOKEN` to access the file because it lacks the necessary
172+
# scope to read repository contents).
173+
POST_DEPLOYMENT_SCRIPT_ZIP_BASE64="$(
174+
cd azure-self-hosted-runners &&
175+
zip -9 tmp.zip post-deployment-script.ps1 >&2 &&
176+
base64 -w 0 tmp.zip
177+
)"
178+
179+
PUBLIC_IP_ADDRESS_NAME1="${{ github.repository_visibility != 'private' && format('{0}-ip', steps.generate-vm-name.outputs.vm_name) || '' }}"
180+
130181
AZURE_ARM_PARAMETERS=$(tr '\n' ' ' <<-END
131182
githubActionsRunnerRegistrationUrl="$ACTIONS_RUNNER_REGISTRATION_URL"
132183
githubActionsRunnerToken="$ACTIONS_RUNNER_TOKEN"
133-
postDeploymentPsScriptUrl="$POST_DEPLOYMENT_SCRIPT_URL"
184+
postDeploymentScriptZipBase64="$POST_DEPLOYMENT_SCRIPT_ZIP_BASE64"
185+
postDeploymentScriptFileName="post-deployment-script.ps1"
134186
virtualMachineImage="$AZURE_VM_IMAGE"
135187
virtualMachineName="${{ steps.generate-vm-name.outputs.vm_name }}"
136188
virtualMachineSize="$AZURE_VM_TYPE"
137-
publicIpAddressName1="${{ steps.generate-vm-name.outputs.vm_name }}-ip"
189+
publicIpAddressName1="$PUBLIC_IP_ADDRESS_NAME1"
138190
adminUsername="${{ secrets.AZURE_VM_USERNAME }}"
139191
adminPassword="${{ secrets.AZURE_VM_PASSWORD }}"
192+
ephemeral="$EPHEMERAL_RUNNER"
140193
stopService="$DEALLOCATE_IMMEDIATELY"
141194
githubActionsRunnerPath="$ACTIONS_RUNNER_PATH"
142195
location="$AZURE_VM_REGION"
@@ -146,10 +199,12 @@ jobs:
146199
echo "AZURE_ARM_PARAMETERS=$AZURE_ARM_PARAMETERS" >> $GITHUB_ENV
147200
148201
- name: Azure Login
149-
uses: ./.github/workflows/azure-login
202+
uses: azure/login@v2
150203
with:
151-
credentials: ${{ secrets.AZURE_CREDENTIALS }}
152-
204+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
205+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
206+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
207+
153208
- uses: azure/arm-deploy@v2
154209
id: deploy-arm-template
155210
with:
@@ -179,7 +234,7 @@ jobs:
179234
if: always()
180235
env:
181236
CUSTOM_SCRIPT_OUTPUT: ${{ steps.deploy-arm-template.outputs.customScriptInstanceView }}
182-
run: echo "$CUSTOM_SCRIPT_OUTPUT" | jq -r '.substatuses[0].message'
237+
run: echo "$CUSTOM_SCRIPT_OUTPUT" | jq -r '.substatuses[0].message' | sed 's/${{ secrets.GITHUB_TOKEN }}/***/g'
183238

184239
- name: Deallocate the VM for later use
185240
if: env.DEALLOCATE_IMMEDIATELY == 'true'

.github/workflows/delete-self-hosted-runner.yml

+29-7
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,43 @@ on:
1212
env:
1313
ACTIONS_RUNNER_NAME: ${{ github.event.inputs.runner_name }}
1414

15+
permissions:
16+
id-token: write # required for Azure login via OIDC
17+
1518
# The following secrets are required for this workflow to run:
16-
# AZURE_CREDENTIALS - Credentials for the Azure CLI. It's recommended to set up a resource
17-
# group specifically for self-hosted Actions Runners.
18-
# az ad sp create-for-rbac --name "{YOUR_DESCRIPTIVE_NAME_HERE}" --role contributor \
19-
# --scopes /subscriptions/{SUBSCRIPTION_ID_HERE}/resourceGroups/{RESOURCE_GROUP_HERE} \
20-
# --sdk-auth
21-
# AZURE_RESOURCE_GROUP - Resource group to create the runner(s) in
19+
# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource
20+
# group specifically for self-hosted Actions Runners, and to add a federated identity
21+
# to authenticate as the currently-running GitHub workflow.
22+
# az identity create --name <managed-identity-name> -g <resource-group>
23+
# az identity federated-credential create \
24+
# --identity-name <managed-identity-name> \
25+
# --resource-group <resource-group> \
26+
# --name github-workflow \
27+
# --issuer https://token.actions.githubusercontent.com \
28+
# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \
29+
# --audiences api://AzureADTokenExchange
30+
# MSYS_NO_PATHCONV=1 \
31+
# az role assignment create \
32+
# --assignee <client-id-of-managed-identity> \
33+
# --scope '/subscriptions/<subscription-id>/resourceGroups/<resource-group>' \
34+
# --role 'Contributor'
35+
# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which
36+
# the Identity lives)
37+
# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated
38+
# (technically, this is not necessary for `az login --service-principal` with a
39+
# managed identity, but `Azure/login` requires it anyway)
40+
# AZURE_RESOURCE_GROUP - Resource group to find the runner in. It's recommended to set up a resource
41+
# group specifically for self-hosted Actions Runners.
2242
jobs:
2343
delete-runner:
2444
runs-on: ubuntu-latest
2545
steps:
2646
- name: Azure Login
2747
uses: azure/login@v2
2848
with:
29-
creds: ${{ secrets.AZURE_CREDENTIALS }}
49+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
50+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
51+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
3052
- name: Delete VM '${{ env.ACTIONS_RUNNER_NAME }}'
3153
uses: azure/CLI@v2
3254
with:

0 commit comments

Comments
 (0)