Skip to content

Commit

Permalink
feat: add new authentication method for GitLab >= 16 (#876)
Browse files Browse the repository at this point in the history
## Description

GitLab released a new [authentication architecture for their
runners](https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens)
since the version 16.0.0. This MR handle this new architecture while
maintaining backward compatibility.

## Migrations required

Highly recommended as the old GitLab Runner registration method will be
removed with GitLab 17.

Migration steps:
1. create a group access token for your GitLab group (needs the owner
role and the api scope). To be refreshed manually once a year.
2. store the token in a SSM parameter and set the variable
`runner_gitlab.access_token_secure_parameter_store_name` to this SSM
parameter
3. remove `runner_gitlab_registration_config.registration_token`. No
longer needed.
4. add `type = `project or group`, `group_id = <GitLab group number>`
(for group runners) or `project_id = <GitLab project id>` (for project
runners) to the `runner_gitlab_registration_config` section.

## Verification

- running the old authentication method on GitLab 16: success
- running the new authentication method on GitLab 16: success

---------

Signed-off-by: François Bibron <francois.bibron@polyconseil.fr>
Co-authored-by: François Bibron <francois.bibron@polyconseil.fr>
Co-authored-by: Matthias Kay <matthias.kay@hlag.com>
  • Loading branch information
3 people committed Nov 3, 2023
1 parent 620459a commit c870745
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 18 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"filesha",
"formatlist",
"gitter",
"glrt",
"glrunners",
"instancelifecycle",
"kics",
Expand Down
47 changes: 46 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ The base image used to host the GitLab Runner agent is the latest available Amaz
this module a hard coded list of AMIs per region was provided. This list has been replaced by a search filter to find the latest
AMI. Setting the filter to `amzn2-ami-hvm-2.0.20200207.1-x86_64-ebs` will allow you to version lock the target AMI if needed.

> 💥 **If you are using GitLab >= 16.0.0**: `registration_token` will be deprecated!
>GitLab >= 16.0.0 has removed the `registration_token` since they are working on a [new token architecture](https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens/). This module handle these changes, you need to provide a personal access token with `api` scope for the runner to authenticate itself.
>The workflow is as follows ([migration steps](https://github.com/cattle-ops/terraform-aws-gitlab-runner/pull/876)):
>1. The runner make an API call (with the access token) to create a new runner on GitLab depending on its type (`instance`, `group` or `project`).
>2. GitLab answers with a token prefixed by `glrt-` and we put it in SSM.
>3. The runner will get the config from `/etc/gitlab-runner/config.toml` and will listen for new jobs from your GitLab instance.
## Install the module

Run `terraform init` to initialize Terraform. Next you can run `terraform plan` to inspect the resources that will be created.
Expand All @@ -36,7 +45,7 @@ terraform destroy

## Scenarios

### Scenario: Basic usage
### Scenario: Basic usage on GitLab **< 16.0.0**

Below is a basic examples of usages of the module. Regarding the dependencies such as a VPC, have a look at the [default example](https://github.com/cattle-ops/terraform-aws-gitlab-runner/tree/main/examples/runner-default).

Expand Down Expand Up @@ -69,6 +78,42 @@ subnet_ids = module.vpc.private_subnets
}
```

### Scenario: Basic usage on GitLab **>= 16.0.0**

Below is a basic examples of usages of the module if your GitLab instance version is >= 16.0.0.

```hcl
module "runner" {
# https://registry.terraform.io/modules/cattle-ops/gitlab-runner/aws/
source = "cattle-ops/gitlab-runner/aws"
aws_region = "eu-west-1"
environment = "spot-runners"
vpc_id = module.vpc.vpc_id
subnet_ids_gitlab_runner = module.vpc.private_subnets
subnet_id_runners = element(module.vpc.private_subnets, 0)
runners_name = "docker-default"
runners_gitlab_url = "https://gitlab.com"
runner_gitlab_access_token_secure_parameter_store_name = "gitlab_access_token_ssm__name"
runner_gitlab_registration_config = {
type = "instance" # or "group" or "project"
# group_id = 1234 # for "group"
# project_id = 5678 # for "project"
tag_list = "docker"
description = "runner default"
locked_to_project = "true"
run_untagged = "false"
maximum_timeout = "3600"
}
}
```

### Scenario: Multi-region deployment

Name clashes due to multi-region deployments for global AWS resources create by this module (IAM, S3) can be avoided by including a
Expand Down
7 changes: 6 additions & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,23 @@ locals {
secure_parameter_store_gitlab_runner_registration_token_name = var.runner_gitlab_registration_token_secure_parameter_store_name
secure_parameter_store_runner_token_key = local.secure_parameter_store_runner_token_key
secure_parameter_store_runner_sentry_dsn = local.secure_parameter_store_runner_sentry_dsn
secure_parameter_store_gitlab_token_name = var.runner_gitlab.access_token_secure_parameter_store_name
secure_parameter_store_region = data.aws_region.current.name
gitlab_runner_registration_token = lookup(var.runner_gitlab_registration_config, "registration_token", "__GITLAB_REGISTRATION_TOKEN_FROM_SSM__")
gitlab_runner_registration_token = var.runner_gitlab_registration_config.registration_token
gitlab_runner_description = var.runner_gitlab_registration_config["description"]
gitlab_runner_tag_list = var.runner_gitlab_registration_config["tag_list"]
gitlab_runner_locked_to_project = var.runner_gitlab_registration_config["locked_to_project"]
gitlab_runner_run_untagged = var.runner_gitlab_registration_config["run_untagged"]
gitlab_runner_maximum_timeout = var.runner_gitlab_registration_config["maximum_timeout"]
gitlab_runner_type = var.runner_gitlab_registration_config["type"]
gitlab_runner_group_id = var.runner_gitlab_registration_config["group_id"]
gitlab_runner_project_id = var.runner_gitlab_registration_config["project_id"]
gitlab_runner_access_level = var.runner_gitlab_registration_config.access_level
sentry_dsn = var.runner_manager.sentry_dsn
public_key = var.runner_worker_docker_machine_fleet.enable == true ? tls_private_key.fleet[0].public_key_openssh : ""
use_fleet = var.runner_worker_docker_machine_fleet.enable
private_key = var.runner_worker_docker_machine_fleet.enable == true ? tls_private_key.fleet[0].private_key_pem : ""
use_new_runner_authentication_gitlab_16 = var.runner_gitlab_registration_config.type != ""
})

template_runner_config = templatefile("${path.module}/template/runner-config.tftpl",
Expand Down
2 changes: 1 addition & 1 deletion outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ output "runner_launch_template_name" {

output "runner_user_data" {
description = "(Deprecated) The user data of the Gitlab Runner Agent's launch template. Set `var.debug.output_runner_user_data_to_file` to true to write `user_data.sh`."
value = local.template_user_data
value = nonsensitive(local.template_user_data)
}

output "runner_config_toml_rendered" {
Expand Down
50 changes: 42 additions & 8 deletions template/gitlab-runner.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,49 @@ then
[[ "$valid_token_response" != "200" ]] && valid_token=false
fi

gitlab_runner_registration_token=${gitlab_runner_registration_token}
# fetch registration token from SSM
if [[ "$gitlab_runner_registration_token" == "__GITLAB_REGISTRATION_TOKEN_FROM_SSM__" ]]
then
gitlab_runner_registration_token=$(aws ssm get-parameter --name "${secure_parameter_store_gitlab_runner_registration_token_name}" --with-decryption --region "${secure_parameter_store_region}" | jq -r ".Parameter | .Value")
fi

if [[ "${runners_token}" == "__REPLACED_BY_USER_DATA__" && "$token" == "null" ]] || [[ "$valid_token" == "false" ]]
then
token=$(curl ${curl_cacert} --request POST -L "${runners_gitlab_url}/api/v4/runners" \
if [ "${use_new_runner_authentication_gitlab_16}" == "true" ]
then
runner_type_param=""
if [ "${gitlab_runner_type}" = "group" ]; then
if [ -z "${gitlab_runner_group_id}" ]; then
echo "ERROR: If the runner type is group, you must specify a group_id".
exit 1
fi
runner_type_param='--form group_id=${gitlab_runner_group_id}'
elif [ "${gitlab_runner_type}" = "project" ]; then
if [ -z "${gitlab_runner_project_id}" ]; then
echo "ERROR: If the runner type is project_type, you must specify a project_id".
exit 1
fi
runner_type_param='--form project_id=${gitlab_runner_project_id}'
fi

# fetch gitlab token from SSM
gitlab_token=$(aws ssm get-parameter --name "${secure_parameter_store_gitlab_token_name}" --with-decryption --region "${secure_parameter_store_region}" | jq -r ".Parameter | .Value")

token=$(curl ${curl_cacert} --request POST -L "${runners_gitlab_url}/api/v4/user/runners" \
--header "private-token: $gitlab_token" \
--form "tag_list=${gitlab_runner_tag_list}" \
--form "description=${gitlab_runner_description}" \
--form "locked=${gitlab_runner_locked_to_project}" \
--form "run_untagged=${gitlab_runner_run_untagged}" \
--form "maximum_timeout=${gitlab_runner_maximum_timeout}" \
--form "runner_type=${gitlab_runner_type}_type" \
$runner_type_param \
--form "access_level=${gitlab_runner_access_level}" \
| jq -r '.token')
else
gitlab_runner_registration_token=${gitlab_runner_registration_token}

# fetch registration token from SSM
if [[ "$gitlab_runner_registration_token" == "__GITLAB_REGISTRATION_TOKEN_FROM_SSM__" ]]
then
gitlab_runner_registration_token=$(aws ssm get-parameter --name "${secure_parameter_store_gitlab_runner_registration_token_name}" --with-decryption --region "${secure_parameter_store_region}" | jq -r ".Parameter | .Value")
fi

token=$(curl ${curl_cacert} --request POST -L "${runners_gitlab_url}/api/v4/runners" \
--form "token=$gitlab_runner_registration_token" \
--form "tag_list=${gitlab_runner_tag_list}" \
--form "description=${gitlab_runner_description}" \
Expand All @@ -50,6 +83,7 @@ then
--form "maximum_timeout=${gitlab_runner_maximum_timeout}" \
--form "access_level=${gitlab_runner_access_level}" \
| jq -r .token)
fi
aws ssm put-parameter --overwrite --type SecureString --name "${secure_parameter_store_runner_token_key}" --value="$token" --region "${secure_parameter_store_region}"
fi

Expand Down
23 changes: 16 additions & 7 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,23 @@ variable "runner_cloudwatch" {
variable "runner_gitlab_registration_config" {
description = "Configuration used to register the Runner. See the README for an example, or reference the examples in the examples directory of this repo. There is also a good GitLab documentation available at: https://docs.gitlab.com/ee/ci/runners/configure_runners.html"
type = object({
registration_token = optional(string, "")
registration_token = optional(string, "__GITLAB_REGISTRATION_TOKEN_FROM_SSM__")
tag_list = optional(string, "")
description = optional(string, "")
type = optional(string, "") # mandatory if gitlab_runner_version >= 16.0.0
group_id = optional(string, "") # mandatory if type is group
project_id = optional(string, "") # mandatory if type is project
locked_to_project = optional(string, "")
run_untagged = optional(string, "")
maximum_timeout = optional(string, "")
access_level = optional(string, "not_protected") # this is the only mandatory field calling the GitLab get token for executor operation
})

default = {}
validation {
condition = contains(["group", "project", "instance", ""], var.runner_gitlab_registration_config.type)
error_message = "The executor currently supports `group`, `project` or `instance`."
}
}

variable "runner_gitlab" {
Expand All @@ -319,14 +326,16 @@ variable "runner_gitlab" {
runner_version = Version of the [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/-/releases).
url = URL of the GitLab instance to connect to.
url_clone = URL of the GitLab instance to clone from. Use only if the agent can’t connect to the GitLab URL.
access_token_secure_parameter_store_name = The name of the SSM parameter to read the GitLab access token from. It must have the `api` scope and be pre created.
EOT
type = object({
ca_certificate = optional(string, "")
certificate = optional(string, "")
registration_token = optional(string, "__REPLACED_BY_USER_DATA__")
runner_version = optional(string, "15.8.2")
url = optional(string, "")
url_clone = optional(string, "")
ca_certificate = optional(string, "")
certificate = optional(string, "")
registration_token = optional(string, "__REPLACED_BY_USER_DATA__")
runner_version = optional(string, "15.8.2")
url = optional(string, "")
url_clone = optional(string, "")
access_token_secure_parameter_store_name = optional(string, "gitlab-runner-access-token")
})
}

Expand Down

0 comments on commit c870745

Please sign in to comment.