Skip to content

Commit e85412a

Browse files
committed
Allow run runner in org for fine grained token permissions
1 parent fb91019 commit e85412a

File tree

5 files changed

+26
-9
lines changed

5 files changed

+26
-9
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ Use the following steps to prepare your workflow for running on your EC2 self-ho
156156
157157
**2. Prepare GitHub personal access token**
158158

159-
1. Create a new GitHub personal access token with the `repo` scope.
160-
The action will use the token for self-hosted runners management in the GitHub account on the repository level.
159+
1. Create a fine-grained personal access token with the required permissions:
160+
- **Repository-level runners (default):** Repository permissions: Administration (read/write), Contents (read)
161+
- **Organization-level runners:** Organization permissions: Self-hosted runners (read/write)
162+
Repository permissions: Contents (read)
161163
2. Add the token to GitHub secrets.
162164

163165
**3. Prepare EC2 image**
@@ -205,7 +207,7 @@ Now you're ready to go!
205207
|               Name               | Required | Description |
206208
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
207209
| `mode` | Always required. | Specify here which mode you want to use: <br> - `start` - to start a new runner; <br> - `stop` - to stop the previously created runner. |
208-
| `github-token` | Always required. | GitHub Personal Access Token with the `repo` scope assigned. |
210+
| `github-token` | Always required. | Fine-grained GitHub Personal Access Token with appropriate permissions (see setup instructions above). |
209211
| `ec2-image-id` | Required if you use the `start` mode. | EC2 Image Id (AMI). <br><br> The new runner will be launched from this image. <br><br> The action is compatible with Amazon Linux 2 images. |
210212
| `ec2-instance-type` | Required if you use the `start` mode. | EC2 Instance Type. |
211213
| `subnet-id` | Required if you use the `start` mode. | VPC Subnet Id. <br><br> The subnet should belong to the same VPC as the specified security group. |
@@ -224,6 +226,7 @@ Now you're ready to go!
224226
| `ec2-volume-size` | Optional | Defines the size of the EC2 Volume in GB, will use the AWS default of 8 GB if not provided. |
225227
| `ec2-device-name` | Optional | Defines the device name used for the root volume. |
226228
| `ec2-volume-type` | Optional | Defines the device type used for the root volume. |
229+
| `run-runner-in-org` | Optional | Default: false. When set to true, the runner will be registered at the organization level instead of the repository level. This allows using fine-grained personal access tokens with only the "Self-hosted runners" organization permission and "Contents" repository permission, avoiding the need for repository admin permissions. |
227230

228231
### Environment variables
229232

@@ -270,6 +273,8 @@ jobs:
270273
with:
271274
mode: start
272275
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
276+
# Optional: Use organization-level runner for enhanced security
277+
# run-runner-in-org: true
273278
ec2-image-id: ami-123
274279
ec2-instance-type: t3.nano
275280
subnet-id: subnet-123
@@ -310,6 +315,8 @@ jobs:
310315
with:
311316
mode: stop
312317
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
318+
# Optional: Use organization-level runner for enhanced security
319+
# run-runner-in-org: true
313320
label: ${{ needs.start-runner.outputs.label }}
314321
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
315322
```

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ inputs:
112112
description: >-
113113
Specify user under whom the runner service should run
114114
required: false
115+
run-runner-in-org:
116+
description: >-
117+
Runner is run in repo by default, but can also be run under the org
118+
required: false
115119
ec2-volume-size:
116120
description: >-
117121
EC2 volume size in GB.

dist/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145230,6 +145230,7 @@ class Config {
145230145230
subnetId: core.getInput('subnet-id'),
145231145231
runAsService: core.getInput('run-runner-as-service') === 'true',
145232145232
runAsUser: core.getInput('run-runner-as-user'),
145233+
runInOrgRunner: core.getInput('run-runner-in-org') === 'true',
145233145234
ec2VolumeSize: core.getInput('ec2-volume-size'),
145234145235
ec2DeviceName: core.getInput('ec2-device-name'),
145235145236
ec2VolumeType: core.getInput('ec2-volume-type'),
@@ -145366,13 +145367,15 @@ const github = __nccwpck_require__(5438);
145366145367
const _ = __nccwpck_require__(250);
145367145368
const config = __nccwpck_require__(4570);
145368145369

145370+
const runnerBasePath = config.input.runInOrgRunner ? "/orgs/{owner}" : "/repos/{owner}/{repo}"
145371+
145369145372
// use the unique label to find the runner
145370145373
// as we don't have the runner's id, it's not possible to get it in any other way
145371145374
async function getRunner(label) {
145372145375
const octokit = github.getOctokit(config.input.githubToken);
145373145376

145374145377
try {
145375-
const runners = await octokit.paginate('GET /repos/{owner}/{repo}/actions/runners', config.githubContext);
145378+
const runners = await octokit.paginate(`GET ${runnerBasePath}/actions/runners`, config.githubContext);
145376145379
const foundRunners = _.filter(runners, { labels: [{ name: label }] });
145377145380
return foundRunners.length > 0 ? foundRunners[0] : null;
145378145381
} catch (error) {
@@ -145385,7 +145388,7 @@ async function getRegistrationToken() {
145385145388
const octokit = github.getOctokit(config.input.githubToken);
145386145389

145387145390
try {
145388-
const response = await octokit.request('POST /repos/{owner}/{repo}/actions/runners/registration-token', config.githubContext);
145391+
const response = await octokit.request(`POST ${runnerBasePath}/actions/runners/registration-token`, config.githubContext);
145389145392
core.info('GitHub Registration Token is received');
145390145393
return response.data.token;
145391145394
} catch (error) {
@@ -145405,7 +145408,7 @@ async function removeRunner() {
145405145408
}
145406145409

145407145410
try {
145408-
await octokit.request('DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}', _.merge(config.githubContext, { runner_id: runner.id }));
145411+
await octokit.request(`DELETE ${runnerBasePath}/actions/runners/{runner_id}`, _.merge(config.githubContext, { runner_id: runner.id }));
145409145412
core.info(`GitHub self-hosted runner ${runner.name} is removed`);
145410145413
return;
145411145414
} catch (error) {

src/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Config {
2121
subnetId: core.getInput('subnet-id'),
2222
runAsService: core.getInput('run-runner-as-service') === 'true',
2323
runAsUser: core.getInput('run-runner-as-user'),
24+
runInOrgRunner: core.getInput('run-runner-in-org') === 'true',
2425
ec2VolumeSize: core.getInput('ec2-volume-size'),
2526
ec2DeviceName: core.getInput('ec2-device-name'),
2627
ec2VolumeType: core.getInput('ec2-volume-type'),

src/gh.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ const github = require('@actions/github');
33
const _ = require('lodash');
44
const config = require('./config');
55

6+
const runnerBasePath = config.input.runInOrgRunner ? "/orgs/{owner}" : "/repos/{owner}/{repo}"
7+
68
// use the unique label to find the runner
79
// as we don't have the runner's id, it's not possible to get it in any other way
810
async function getRunner(label) {
911
const octokit = github.getOctokit(config.input.githubToken);
1012

1113
try {
12-
const runners = await octokit.paginate('GET /repos/{owner}/{repo}/actions/runners', config.githubContext);
14+
const runners = await octokit.paginate(`GET ${runnerBasePath}/actions/runners`, config.githubContext);
1315
const foundRunners = _.filter(runners, { labels: [{ name: label }] });
1416
return foundRunners.length > 0 ? foundRunners[0] : null;
1517
} catch (error) {
@@ -22,7 +24,7 @@ async function getRegistrationToken() {
2224
const octokit = github.getOctokit(config.input.githubToken);
2325

2426
try {
25-
const response = await octokit.request('POST /repos/{owner}/{repo}/actions/runners/registration-token', config.githubContext);
27+
const response = await octokit.request(`POST ${runnerBasePath}/actions/runners/registration-token`, config.githubContext);
2628
core.info('GitHub Registration Token is received');
2729
return response.data.token;
2830
} catch (error) {
@@ -42,7 +44,7 @@ async function removeRunner() {
4244
}
4345

4446
try {
45-
await octokit.request('DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}', _.merge(config.githubContext, { runner_id: runner.id }));
47+
await octokit.request(`DELETE ${runnerBasePath}/actions/runners/{runner_id}`, _.merge(config.githubContext, { runner_id: runner.id }));
4648
core.info(`GitHub self-hosted runner ${runner.name} is removed`);
4749
return;
4850
} catch (error) {

0 commit comments

Comments
 (0)