From 6552531d808a886c8078f68456a8afe0f8d407b8 Mon Sep 17 00:00:00 2001 From: Niek Palm Date: Wed, 6 Oct 2021 23:20:07 +0200 Subject: [PATCH] feat: Ignore github managed labels and add check disable option --- README.md | 21 ++++++++++-- main.tf | 1 + modules/webhook/README.md | 3 +- modules/webhook/lambdas/webhook/package.json | 2 +- .../webhook/src/webhook/handler.test.ts | 32 +++++++++++++++---- .../lambdas/webhook/src/webhook/handler.ts | 22 +++++++++---- modules/webhook/variables.tf | 6 ++++ modules/webhook/webhook.tf | 9 +++--- variables.tf | 6 ++++ 9 files changed, 82 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 24a1acbbc5..ee7d737ffc 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,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 | @@ -354,12 +371,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`). |
object({
key_base64 = string
id = string
client_id = string
client_secret = string
webhook_secret = string
})
| 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. |
list(object({
cron = string
timeZone = string
idleCount = number
}))
| `[]` | 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 | @@ -416,7 +434,6 @@ No requirements. | runners | n/a | | ssm\_parameters | n/a | | webhook | n/a | - ## Contribution diff --git a/main.tf b/main.tf index 3745dc3a27..89a2889582 100644 --- a/main.tf +++ b/main.tf @@ -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 diff --git a/modules/webhook/README.md b/modules/webhook/README.md index 86d029bbb9..9a2897ba29 100644 --- a/modules/webhook/README.md +++ b/modules/webhook/README.md @@ -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 | @@ -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. |
object({
id = string
arn = string
})
| n/a | yes | diff --git a/modules/webhook/lambdas/webhook/package.json b/modules/webhook/lambdas/webhook/package.json index 2e2a57e3a2..91f2865b8f 100644 --- a/modules/webhook/lambdas/webhook/package.json +++ b/modules/webhook/lambdas/webhook/package.json @@ -42,4 +42,4 @@ "@octokit/webhooks": "^9.12.0", "aws-lambda": "^1.0.6" } -} +} \ No newline at end of file diff --git a/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts b/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts index 797257630a..401c53d8fc 100644 --- a/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts +++ b/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts @@ -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( @@ -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: { @@ -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( @@ -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: { diff --git a/modules/webhook/lambdas/webhook/src/webhook/handler.ts b/modules/webhook/lambdas/webhook/src/webhook/handler.ts index 1b2c4d5d08..038df6a1b1 100644 --- a/modules/webhook/lambdas/webhook/src/webhook/handler.ts +++ b/modules/webhook/lambdas/webhook/src/webhook/handler.ts @@ -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; } @@ -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; 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); // 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; } diff --git a/modules/webhook/variables.tf b/modules/webhook/variables.tf index d9187f9aa4..29b989c04f 100644 --- a/modules/webhook/variables.tf +++ b/modules/webhook/variables.tf @@ -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 +} \ No newline at end of file diff --git a/modules/webhook/webhook.tf b/modules/webhook/webhook.tf index 5e7b8373cf..febdc2a8dc 100644 --- a/modules/webhook/webhook.tf +++ b/modules/webhook/webhook.tf @@ -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)) } } diff --git a/variables.tf b/variables.tf index 2a9b831be2..eadafe1128 100644 --- a/variables.tf +++ b/variables.tf @@ -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 +}