Skip to content

Commit

Permalink
feat: Ignore github managed labels and add check disable option (#1244)
Browse files Browse the repository at this point in the history
  • Loading branch information
npalm authored Oct 8, 2021
1 parent 570949a commit 859fa38
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 20 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,23 @@ No requirements.
| aws | n/a |
| random | n/a |

## Modules

| Name | Source | Version |
|------|--------|---------|
| runner_binaries | ./modules/runner-binaries-syncer | |
| runners | ./modules/runners | |
| ssm | ./modules/ssm | |
| webhook | ./modules/webhook | |

## Resources

| Name |
|------|
| [aws_resourcegroups_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/resourcegroups_group) |
| [aws_sqs_queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) |
| [random_string](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) |

## Inputs

| Name | Description | Type | Default | Required |
Expand All @@ -353,12 +370,13 @@ No requirements.
| cloudwatch\_config | (optional) Replaces the module default cloudwatch log config. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html for details. | `string` | `null` | no |
| create\_service\_linked\_role\_spot | (optional) create the serviced linked role for spot instances that is required by the scale-up lambda. | `bool` | `false` | no |
| delay\_webhook\_event | The number of seconds the event accepted by the webhook is invisible on the queue before the scale up lambda will receive the event. | `number` | `30` | no |
| disable\_check\_wokflow\_job\_labels | Disable the the check of workflow labels for received workflow job events. | `bool` | `false` | no |
| enable\_cloudwatch\_agent | Enabling the cloudwatch agent on the ec2 runner instances, the runner contains default config. Configuration can be overridden via `cloudwatch_config`. | `bool` | `true` | no |
| enable\_organization\_runners | Register runners to organization, instead of repo level | `bool` | `false` | no |
| enable\_ssm\_on\_runners | Enable to allow access the runner instances for debugging purposes via SSM. Note that this adds additional permissions to the runner instances. | `bool` | `false` | no |
| environment | A name that identifies the environment, used as prefix and for tagging. | `string` | n/a | yes |
| ghes\_ssl\_verify | GitHub Enterprise SSL verification. Set to 'false' when custom certificate (chains) is used for GitHub Enterprise Server (insecure). | `bool` | `true` | no |
| ghes\_url | GitHub Enterprise Server URL. Example: https://github.internal.co - DO NOT SET IF USING PUBLIC GITHUB | `string` | `null` | no |
| ghes\_ssl\_verify | GitHub Enterprise SSL verification. Set to `false` when custom certificate (chains) is used for GitHub Enterprise Server (insecure). | `bool` | `true` | no |
| github\_app | GitHub app parameters, see your github app. Ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`). | <pre>object({<br> key_base64 = string<br> id = string<br> client_id = string<br> client_secret = string<br> webhook_secret = string<br> })</pre> | n/a | yes |
| idle\_config | List of time period that can be defined as cron expression to keep a minimum amount of runners active instead of scaling down to 0. By defining this list you can ensure that in time periods that match the cron expression within 5 seconds a runner is kept idle. | <pre>list(object({<br> cron = string<br> timeZone = string<br> idleCount = number<br> }))</pre> | `[]` | no |
| instance\_profile\_path | The path that will be added to the instance\_profile, if not set the environment name will be used. | `string` | `null` | no |
Expand Down Expand Up @@ -415,7 +433,6 @@ No requirements.
| runners | n/a |
| ssm\_parameters | n/a |
| webhook | n/a |

<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

## Contribution
Expand Down
1 change: 1 addition & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module "webhook" {
lambda_timeout = var.webhook_lambda_timeout
logging_retention_in_days = var.logging_retention_in_days
runner_extra_labels = var.runner_extra_labels
disable_check_wokflow_job_labels = var.disable_check_wokflow_job_labels

role_path = var.role_path
role_permissions_boundary = var.role_permissions_boundary
Expand Down
3 changes: 2 additions & 1 deletion modules/webhook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ No Modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| aws\_region | AWS region. | `string` | n/a | yes |
| disable\_check\_wokflow\_job\_labels | Disable the the check of workflow labels. | `bool` | `false` | no |
| environment | A name that identifies the environment, used as prefix and for tagging. | `string` | n/a | yes |
| github\_app\_webhook\_secret\_arn | n/a | `string` | n/a | yes |
| kms\_key\_arn | Optional CMK Key ARN to be used for Parameter Store. | `string` | `null` | no |
Expand All @@ -76,7 +77,7 @@ No Modules.
| lambda\_zip | File location of the lambda zip file. | `string` | `null` | no |
| logging\_retention\_in\_days | Specifies the number of days you want to retain log events for the lambda log group. Possible values are: 0, 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. | `number` | `7` | no |
| repository\_white\_list | List of repositories allowed to use the github app | `list(string)` | `[]` | no |
| role\_path | The path that will be added to the role, if not set the environment name will be used. | `string` | `null` | no |
| role\_path | The path that will be added to the role; if not set, the environment name will be used. | `string` | `null` | no |
| role\_permissions\_boundary | Permissions boundary that will be added to the created role for the lambda. | `string` | `null` | no |
| runner\_extra\_labels | Extra labels for the runners (GitHub). Separate each label by a comma | `string` | `""` | no |
| sqs\_build\_queue | SQS queue to publish accepted build events. | <pre>object({<br> id = string<br> arn = string<br> })</pre> | n/a | yes |
Expand Down
2 changes: 1 addition & 1 deletion modules/webhook/lambdas/webhook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@
"@octokit/webhooks": "^9.12.0",
"aws-lambda": "^1.0.6"
}
}
}
32 changes: 26 additions & 6 deletions modules/webhook/lambdas/webhook/src/webhook/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ describe('handler', () => {
});

describe('Test for workflowjob event: ', () => {
beforeEach(() => {
process.env.DISABLE_CHECK_WORKFLOW_JOB_LABELS = 'false';
});
it('handles workflow job events', async () => {
const event = JSON.stringify(workflowjob_event);
const resp = await handle(
Expand Down Expand Up @@ -139,8 +142,8 @@ describe('handler', () => {
expect(sendActionRequest).toBeCalled();
});

it('Check runner a runner with multiple labels accept a job with a subset of labels.', async () => {
process.env.RUNNER_LABELS = '["test", "linux"]';
it('Check runner a self hosted runner will run a job marked with only self-hosted', async () => {
process.env.RUNNER_LABELS = '["test", "test2"]';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
Expand All @@ -156,13 +159,13 @@ describe('handler', () => {
expect(sendActionRequest).toBeCalled();
});

it('Check runner labels in mixed order', async () => {
process.env.RUNNER_LABELS = '["test", "linux"]';
it('Check runner labels for a strict job (2 labels should match)', async () => {
process.env.RUNNER_LABELS = '["test", "test2"]';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
...workflowjob_event.workflow_job,
labels: ['self-hosted', 'linux', 'test'],
labels: ['self-hosted', 'linux', 'test', 'test2'],
},
});
const resp = await handle(
Expand All @@ -173,8 +176,25 @@ describe('handler', () => {
expect(sendActionRequest).toBeCalled();
});

it('Check event is accepted for disabled workflow check', async () => {
process.env.DISABLE_CHECK_WORKFLOW_JOB_LABELS = 'true';
process.env.RUNNER_LABELS = '["test", "no-check"]';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
...workflowjob_event.workflow_job,
labels: ['self-hosted', 'linux', 'test', 'test2'],
},
});
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'workflow_job' },
event,
);
expect(resp).toBe(200);
expect(sendActionRequest).toBeCalled();
});
it('Check not allowed runner label is declined', async () => {
process.env.RUNNER_LABELS = '["test", "linux"]';
process.env.RUNNER_LABELS = '["test"]';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
Expand Down
22 changes: 16 additions & 6 deletions modules/webhook/lambdas/webhook/src/webhook/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ async function handleWorkflowJob(body: WorkflowJob, githubEvent: string): Promis
return 403;
}

if (isRunnerNotAllowed(body)) {
const disableCheckWorkflowJobLabelsEnv = process.env.DISABLE_CHECK_WORKFLOW_JOB_LABELS || 'false';
const disableCheckWorkflowJobLabels = JSON.parse(disableCheckWorkflowJobLabelsEnv) as boolean;
if (!disableCheckWorkflowJobLabels && !canRunJob(body)) {
console.error(`Received event contains runner labels '${body.workflow_job.labels}' that are not accepted.`);
return 403;
}
Expand Down Expand Up @@ -115,24 +117,32 @@ async function handleCheckRun(body: CheckRunEvent, githubEvent: string): Promise
}

function isRepoNotAllowed(body: WorkflowJob | CheckRunEvent): boolean {
const repositoryWhiteListEnv = (process.env.REPOSITORY_WHITE_LIST as string) || '[]';
const repositoryWhiteListEnv = process.env.REPOSITORY_WHITE_LIST || '[]';
const repositoryWhiteList = JSON.parse(repositoryWhiteListEnv) as Array<string>;

return repositoryWhiteList.length > 0 && !repositoryWhiteList.includes(body.repository.full_name);
}

function isRunnerNotAllowed(job: WorkflowJob): boolean {
const runnerLabelsEnv = (process.env.RUNNER_LABELS as string) || '[]';
function canRunJob(job: WorkflowJob): boolean {
const runnerLabelsEnv = process.env.RUNNER_LABELS || '[]';
const runnerLabels = new Set(JSON.parse(runnerLabelsEnv) as Array<string>);

// ensure the self-hosted label is in the list.
runnerLabels.add('self-hosted');
const runnerMatch = job.workflow_job.labels.every((l) => runnerLabels.has(l));
const workflowJobLabels = job.workflow_job.labels;

// eslint-disable-next-line max-len
// GitHub managed labels: https://docs.github.com/en/actions/hosting-your-own-runners/using-self-hosted-runners-in-a-workflow#using-default-labels-to-route-jobs
const githubManagedLabels = ['self-hosted', 'linux', 'macOS', 'windows', 'x64', 'ARM', 'ARM64'];
// Remove GitHub managed labels
const customWorkflowJobLabels = workflowJobLabels.filter((l) => githubManagedLabels.indexOf(l) < 0);

const runnerMatch = customWorkflowJobLabels.every((l) => runnerLabels.has(l));

console.debug(
`Received workflow job event with labels: '${JSON.stringify(job.workflow_job.labels)}'. The event does ${
runnerMatch ? '' : 'NOT '
}match the configured labels: '${Array.from(runnerLabels).join(',')}'`,
);
return !runnerMatch;
return runnerMatch;
}
6 changes: 6 additions & 0 deletions modules/webhook/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ variable "runner_extra_labels" {
type = string
default = ""
}

variable "disable_check_wokflow_job_labels" {
description = "Disable the the check of workflow labels."
type = bool
default = false
}
9 changes: 5 additions & 4 deletions modules/webhook/webhook.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ resource "aws_lambda_function" "webhook" {

environment {
variables = {
ENVIRONMENT = var.environment
SQS_URL_WEBHOOK = var.sqs_build_queue.id
REPOSITORY_WHITE_LIST = jsonencode(var.repository_white_list)
RUNNER_LABELS = jsonencode(split(",", var.runner_extra_labels))
DISABLE_CHECK_WORKFLOW_JOB_LABELS = var.disable_check_wokflow_job_labels
ENVIRONMENT = var.environment
SQS_URL_WEBHOOK = var.sqs_build_queue.id
REPOSITORY_WHITE_LIST = jsonencode(var.repository_white_list)
RUNNER_LABELS = jsonencode(split(",", var.runner_extra_labels))
}
}

Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,9 @@ variable "runner_egress_rules" {
description = null
}]
}

variable "disable_check_wokflow_job_labels" {
description = "Disable the the check of workflow labels for received workflow job events."
type = bool
default = false
}

0 comments on commit 859fa38

Please sign in to comment.