diff --git a/README.md b/README.md index f3fd886264..f309425483 100644 --- a/README.md +++ b/README.md @@ -458,6 +458,7 @@ In case the setup does not work as intended follow the trace of events: | [runner\_ec2\_tags](#input\_runner\_ec2\_tags) | Map of tags that will be added to the launch template instance tag specificatons. | `map(string)` | `{}` | no | | [runner\_egress\_rules](#input\_runner\_egress\_rules) | List of egress rules for the GitHub runner instances. |
list(object({
cidr_blocks = list(string)
ipv6_cidr_blocks = list(string)
prefix_list_ids = list(string)
from_port = number
protocol = string
security_groups = list(string)
self = bool
to_port = number
description = string
}))
|
[
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": null,
"from_port": 0,
"ipv6_cidr_blocks": [
"::/0"
],
"prefix_list_ids": null,
"protocol": "-1",
"security_groups": null,
"self": null,
"to_port": 0
}
]
| no | | [runner\_enable\_workflow\_job\_labels\_check](#input\_runner\_enable\_workflow\_job\_labels\_check) | If set to true all labels in the workflow job even are matched agaist the custom labels and GitHub labels (os, architecture and `self-hosted`). When the labels are not matching the event is dropped at the webhook. | `bool` | `false` | no | +| [runner\_enable\_workflow\_job\_labels\_check\_all](#input\_runner\_enable\_workflow\_job\_labels\_check\_all) | If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `runner_enable_workflow_job_labels_check` must be true for this to take effect. | `bool` | `true` | no | | [runner\_extra\_labels](#input\_runner\_extra\_labels) | Extra (custom) labels for the runners (GitHub). Separate each label by a comma. Labels checks on the webhook can be enforced by setting `enable_workflow_job_labels_check`. GitHub read-only labels should not be provided. | `string` | `""` | no | | [runner\_group\_name](#input\_runner\_group\_name) | Name of the runner group. | `string` | `"Default"` | no | | [runner\_iam\_role\_managed\_policy\_arns](#input\_runner\_iam\_role\_managed\_policy\_arns) | Attach AWS or customer-managed IAM policies (by ARN) to the runner IAM role | `list(string)` | `[]` | no | diff --git a/main.tf b/main.tf index 3fa6f4baa8..bae5f4321e 100644 --- a/main.tf +++ b/main.tf @@ -111,6 +111,7 @@ module "webhook" { # labels enable_workflow_job_labels_check = var.runner_enable_workflow_job_labels_check + workflow_job_labels_check_all = var.runner_enable_workflow_job_labels_check_all runner_labels = "self-hosted,${var.runner_os},${var.runner_architecture},${var.runner_extra_labels}" role_path = var.role_path diff --git a/modules/webhook/README.md b/modules/webhook/README.md index e890cfee15..8c15816f26 100644 --- a/modules/webhook/README.md +++ b/modules/webhook/README.md @@ -97,6 +97,7 @@ No modules. | [tags](#input\_tags) | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | `map(string)` | `{}` | no | | [webhook\_lambda\_s3\_key](#input\_webhook\_lambda\_s3\_key) | S3 key for webhook lambda function. Required if using S3 bucket to specify lambdas. | `any` | `null` | no | | [webhook\_lambda\_s3\_object\_version](#input\_webhook\_lambda\_s3\_object\_version) | S3 object version for webhook lambda function. Useful if S3 versioning is enabled on source bucket. | `any` | `null` | no | +| [workflow\_job\_labels\_check\_all](#input\_workflow\_job\_labels\_check\_all) | If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `enable_workflow_job_labels_check` must be true for this to take effect. | `bool` | `true` | no | ## Outputs diff --git a/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts b/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts index 4fb2328271..500784638a 100644 --- a/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts +++ b/modules/webhook/lambdas/webhook/src/webhook/handler.test.ts @@ -124,6 +124,7 @@ describe('handler', () => { it('Check runner labels accept test job', async () => { process.env.RUNNER_LABELS = '["self-hosted", "test"]'; process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true'; + process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true'; const event = JSON.stringify({ ...workflowjob_event, workflow_job: { @@ -142,6 +143,7 @@ describe('handler', () => { it('Check runner labels accept job with mixed order.', async () => { process.env.RUNNER_LABELS = '["linux", "test", "self-hosted"]'; process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true'; + process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true'; const event = JSON.stringify({ ...workflowjob_event, workflow_job: { @@ -160,6 +162,7 @@ describe('handler', () => { it('Check webhook does not accept jobs where not all labels are provided in job.', async () => { process.env.RUNNER_LABELS = '["self-hosted", "test", "test2"]'; process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true'; + process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true'; const event = JSON.stringify({ ...workflowjob_event, workflow_job: { @@ -178,6 +181,7 @@ describe('handler', () => { it('Check webhook does not accept jobs where not all labels are supported by the runner.', async () => { process.env.RUNNER_LABELS = '["self-hosted", "x64", "linux", "test"]'; process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true'; + process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true'; const event = JSON.stringify({ ...workflowjob_event, workflow_job: { @@ -192,6 +196,44 @@ describe('handler', () => { expect(resp.statusCode).toBe(202); expect(sendActionRequest).not.toBeCalled; }); + + it('Check webhook will accept jobs with a single acceptable label.', async () => { + process.env.RUNNER_LABELS = '["self-hosted", "x64", "linux", "test"]'; + process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true'; + process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'false'; + const event = JSON.stringify({ + ...workflowjob_event, + workflow_job: { + ...workflowjob_event.workflow_job, + labels: ['x64'], + }, + }); + const resp = await handle( + { 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'workflow_job' }, + event, + ); + expect(resp.statusCode).toBe(201); + expect(sendActionRequest).toBeCalled(); + }); + + it('Check webhook will not accept jobs without correct label when job label check all is false.', async () => { + process.env.RUNNER_LABELS = '["self-hosted", "x64", "linux", "test"]'; + process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true'; + process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'false'; + const event = JSON.stringify({ + ...workflowjob_event, + workflow_job: { + ...workflowjob_event.workflow_job, + labels: ['ubuntu-latest'], + }, + }); + const resp = await handle( + { 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'workflow_job' }, + event, + ); + expect(resp.statusCode).toBe(202); + expect(sendActionRequest).not.toBeCalled; + }); }); describe('Test for check_run event (legacy): ', () => { diff --git a/modules/webhook/lambdas/webhook/src/webhook/handler.ts b/modules/webhook/lambdas/webhook/src/webhook/handler.ts index 795f1ab30c..983a11cf52 100644 --- a/modules/webhook/lambdas/webhook/src/webhook/handler.ts +++ b/modules/webhook/lambdas/webhook/src/webhook/handler.ts @@ -11,7 +11,8 @@ const supportedEvents = ['check_run', 'workflow_job']; const logger = rootLogger.getChildLogger(); export async function handle(headers: IncomingHttpHeaders, body: string): Promise { - const { environment, repositoryWhiteList, enableWorkflowLabelCheck, runnerLabels } = readEnvironmentVariables(); + const { environment, repositoryWhiteList, enableWorkflowLabelCheck, workflowLabelCheckAll, runnerLabels } = + readEnvironmentVariables(); // ensure header keys lower case since github headers can contain capitals. for (const key in headers) { @@ -66,6 +67,7 @@ export async function handle(headers: IncomingHttpHeaders, body: string): Promis payload as WorkflowJobEvent, githubEvent, enableWorkflowLabelCheck, + workflowLabelCheckAll, runnerLabels, ); } else if (githubEvent == 'check_run') { @@ -79,11 +81,13 @@ function readEnvironmentVariables() { const environment = process.env.ENVIRONMENT; const enableWorkflowLabelCheckEnv = process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK || 'false'; const enableWorkflowLabelCheck = JSON.parse(enableWorkflowLabelCheckEnv) as boolean; + const workflowLabelCheckAllEnv = process.env.WORKFLOW_JOB_LABELS_CHECK_ALL || 'false'; + const workflowLabelCheckAll = JSON.parse(workflowLabelCheckAllEnv) as boolean; const repositoryWhiteListEnv = process.env.REPOSITORY_WHITE_LIST || '[]'; const repositoryWhiteList = JSON.parse(repositoryWhiteListEnv) as Array; const runnerLabelsEnv = process.env.RUNNER_LABELS || '[]'; const runnerLabels = JSON.parse(runnerLabelsEnv) as Array; - return { environment, repositoryWhiteList, enableWorkflowLabelCheck, runnerLabels }; + return { environment, repositoryWhiteList, enableWorkflowLabelCheck, workflowLabelCheckAll, runnerLabels }; } async function verifySignature( @@ -117,9 +121,10 @@ async function handleWorkflowJob( body: WorkflowJobEvent, githubEvent: string, enableWorkflowLabelCheck: boolean, + workflowLabelCheckAll: boolean, runnerLabels: string[], ): Promise { - if (enableWorkflowLabelCheck && !canRunJob(body, runnerLabels)) { + if (enableWorkflowLabelCheck && !canRunJob(body, runnerLabels, workflowLabelCheckAll)) { logger.warn( `Received event contains runner labels '${body.workflow_job.labels}' that are not accepted.`, LogFields.print(), @@ -171,10 +176,17 @@ function isRepoNotAllowed(repoFullName: string, repositoryWhiteList: string[]): return repositoryWhiteList.length > 0 && !repositoryWhiteList.includes(repoFullName); } -function canRunJob(job: WorkflowJobEvent, runnerLabels: string[]): boolean { +function canRunJob(job: WorkflowJobEvent, runnerLabels: string[], workflowLabelCheckAll: boolean): boolean { const workflowJobLabels = job.workflow_job.labels; - const runnerMatch = runnerLabels.every((l) => workflowJobLabels.includes(l)); - const jobMatch = workflowJobLabels.every((l) => runnerLabels.includes(l)); + let runnerMatch; + let jobMatch; + if (workflowLabelCheckAll) { + runnerMatch = runnerLabels.every((l) => workflowJobLabels.includes(l)); + jobMatch = workflowJobLabels.every((l) => runnerLabels.includes(l)); + } else { + runnerMatch = runnerLabels.some((l) => workflowJobLabels.includes(l)); + jobMatch = workflowJobLabels.some((l) => runnerLabels.includes(l)); + } const match = jobMatch && runnerMatch; logger.debug( diff --git a/modules/webhook/variables.tf b/modules/webhook/variables.tf index 4fbb825c1a..bf7dcef868 100644 --- a/modules/webhook/variables.tf +++ b/modules/webhook/variables.tf @@ -113,6 +113,12 @@ variable "enable_workflow_job_labels_check" { default = false } +variable "workflow_job_labels_check_all" { + description = "If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `enable_workflow_job_labels_check` must be true for this to take effect." + type = bool + default = true +} + variable "log_type" { description = "Logging format for lambda logging. Valid values are 'json', 'pretty', 'hidden'. " type = string diff --git a/modules/webhook/webhook.tf b/modules/webhook/webhook.tf index ac9939a1f5..da55ed1f8e 100644 --- a/modules/webhook/webhook.tf +++ b/modules/webhook/webhook.tf @@ -14,6 +14,7 @@ resource "aws_lambda_function" "webhook" { environment { variables = { ENABLE_WORKFLOW_JOB_LABELS_CHECK = var.enable_workflow_job_labels_check + WORKFLOW_JOB_LABELS_CHECK_ALL = var.workflow_job_labels_check_all ENVIRONMENT = var.prefix LOG_LEVEL = var.log_level LOG_TYPE = var.log_type diff --git a/variables.tf b/variables.tf index e3d83c435b..e3fdc04599 100644 --- a/variables.tf +++ b/variables.tf @@ -511,6 +511,12 @@ variable "runner_enable_workflow_job_labels_check" { default = false } +variable "runner_enable_workflow_job_labels_check_all" { + description = "If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `runner_enable_workflow_job_labels_check` must be true for this to take effect." + type = bool + default = true +} + variable "runner_ec2_tags" { description = "Map of tags that will be added to the launch template instance tag specificatons." type = map(string)