Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ignore github managed labels and add check disable option #1244

Merged
merged 1 commit into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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`). | <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 @@ -416,7 +434,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
}