Skip to content

Commit 5d88b3b

Browse files
feat(microvm): expose runner config ARN
1 parent d397e7a commit 5d88b3b

7 files changed

Lines changed: 62 additions & 28 deletions

File tree

modules/compute-providers/aws/microvm/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Select it with the `compute_provider.aws.microvm` leaf. The Terraform dispatch k
66

77
MicroVM runners use the provider's fixed 28,800-second (8-hour) lifetime; this is not a Terraform input.
88

9-
The resolved provider-neutral `runner.iam.role` is passed to Lambda as the MicroVM execution role. The provider creates `/github-self-hosted-runners/<prefix>/microvm` with the common observability lifecycle and derives a control-plane-only metadata prefix at `<ssm.paths.root>/<ssm.paths.config>/microvm-metadata`. Scale-up, scale-down, and pool use that non-secret prefix for MicroVM ownership and lifecycle state; the runner role retains value-read and deletion access only on the lane-scoped one-time JIT path, plus runtime logging. When the runner role is supplied externally, its Lambda trust, JIT parameter access, and stream-write permissions remain caller-owned.
9+
The resolved provider-neutral `runner.iam.role` is passed to Lambda as the MicroVM execution role. The provider creates `/github-self-hosted-runners/<prefix>/microvm` with the common observability lifecycle and derives a metadata prefix at `<ssm.paths.root>/<ssm.paths.config>/microvm-metadata`. Scale-up, scale-down, and pool use that non-secret prefix for MicroVM ownership and lifecycle state; the runner role receives read access to the lane metadata child prefix, value-read and deletion access on the lane-scoped one-time JIT path, and runtime logging. MicroVMs sharing the execution role can read the metadata records for that lane. When the runner role is supplied externally, its Lambda trust and these permissions remain caller-owned.
1010

1111
<!-- BEGIN_TF_DOCS -->
1212
## Requirements
@@ -51,7 +51,7 @@ No modules.
5151
| <a name="input_github"></a> [github](#input\_github) | GitHub Enterprise Server settings available to compute-provider bootstrap data.<br/><br/>- `enterprise_server.url`: Optional GitHub Enterprise Server base URL. Null selects GitHub.com.<br/>- `enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server. | <pre>object({<br/> enterprise_server = optional(object({<br/> url = optional(string, null)<br/> ssl_verify = optional(bool, true)<br/> }), {})<br/> })</pre> | `{}` | no |
5252
| <a name="input_observability"></a> [observability](#input\_observability) | Provider-neutral observability settings applied to the provider-managed MicroVM runtime log group.<br/><br/>- `logs.retention_in_days`: CloudWatch Logs retention period.<br/>- `logs.kms_key_id`: Optional KMS key ID or ARN used to encrypt the log group.<br/>- `logs.class`: CloudWatch log-group class.<br/>- `logs.tags`: Tags merged after module-level tags on the log group. | <pre>object({<br/> logs = optional(object({<br/> retention_in_days = optional(number, 180)<br/> kms_key_id = optional(string, null)<br/> class = optional(string, "STANDARD")<br/> tags = optional(map(string), {})<br/> }), {})<br/> })</pre> | `{}` | no |
5353
| <a name="input_prefix"></a> [prefix](#input\_prefix) | Prefix used to identify resources created for the runner configuration. | `string` | `"github-actions"` | no |
54-
| <a name="input_runner"></a> [runner](#input\_runner) | Resolved runner settings consumed by the Lambda MicroVM compute provider.<br/><br/>- `os`: Runner operating system. Lambda MicroVM requires `linux`.<br/>- `architecture`: Runner distribution architecture. Lambda MicroVM requires `arm64`.<br/>- `name_prefix`: Prefix added to registered runner names.<br/>- `run_as_root`: Runs the runner service as root.<br/>- `run_as`: Operating-system user used when `run_as_root` is false.<br/>- `hooks.job_started`: Script installed as the runner job-started hook.<br/>- `hooks.job_completed`: Script installed as the runner job-completed hook.<br/>- `iam.role.arn`: Resolved runner-role ARN used as the MicroVM execution role and referenced by provider policies.<br/>- `iam.role.name`: Resolved runner-role name used by provider resources.<br/>- `iam.role.managed`: Whether runner-config manages the resolved runner role. Callers own an external role and must grant it `ssm:GetParameter` and `ssm:DeleteParameter` on the lane token path plus `logs:CreateLogStream` and `logs:PutLogEvents` on the provider-managed runtime log group.<br/>- `iam.managed_policy_arns`: Common managed-policy ARNs returned with the provider-specific runner policies for attachment by runner-config.<br/>- `iam.path`: IAM path available to provider-managed IAM resources. Null derives the path from `prefix`. | <pre>object({<br/> os = optional(string, "linux")<br/> architecture = optional(string, "arm64")<br/> name_prefix = optional(string, "")<br/> run_as_root = optional(bool, false)<br/> run_as = optional(string, "ec2-user")<br/> hooks = optional(object({<br/> job_started = optional(string, "")<br/> job_completed = optional(string, "")<br/> }), {})<br/> iam = object({<br/> role = object({<br/> arn = string<br/> name = string<br/> managed = optional(bool, true)<br/> })<br/> managed_policy_arns = optional(map(string), {})<br/> path = optional(string, null)<br/> })<br/> })</pre> | n/a | yes |
54+
| <a name="input_runner"></a> [runner](#input\_runner) | Resolved runner settings consumed by the Lambda MicroVM compute provider.<br/><br/>- `os`: Runner operating system. Lambda MicroVM requires `linux`.<br/>- `architecture`: Runner distribution architecture. Lambda MicroVM requires `arm64`.<br/>- `name_prefix`: Prefix added to registered runner names.<br/>- `run_as_root`: Runs the runner service as root.<br/>- `run_as`: Operating-system user used when `run_as_root` is false.<br/>- `hooks.job_started`: Script installed as the runner job-started hook.<br/>- `hooks.job_completed`: Script installed as the runner job-completed hook.<br/>- `iam.role.arn`: Resolved runner-role ARN used as the MicroVM execution role and referenced by provider policies.<br/>- `iam.role.name`: Resolved runner-role name used by provider resources.<br/>- `iam.role.managed`: Whether runner-config manages the resolved runner role. Callers own an external role and must grant it `ssm:GetParameter` on the lane metadata child prefix, `ssm:GetParameter` and `ssm:DeleteParameter` on the lane token path, plus `logs:CreateLogStream` and `logs:PutLogEvents` on the provider-managed runtime log group.<br/>- `iam.managed_policy_arns`: Common managed-policy ARNs returned with the provider-specific runner policies for attachment by runner-config.<br/>- `iam.path`: IAM path available to provider-managed IAM resources. Null derives the path from `prefix`. | <pre>object({<br/> os = optional(string, "linux")<br/> architecture = optional(string, "arm64")<br/> name_prefix = optional(string, "")<br/> run_as_root = optional(bool, false)<br/> run_as = optional(string, "ec2-user")<br/> hooks = optional(object({<br/> job_started = optional(string, "")<br/> job_completed = optional(string, "")<br/> }), {})<br/> iam = object({<br/> role = object({<br/> arn = string<br/> name = string<br/> managed = optional(bool, true)<br/> })<br/> managed_policy_arns = optional(map(string), {})<br/> path = optional(string, null)<br/> })<br/> })</pre> | n/a | yes |
5555
| <a name="input_ssm"></a> [ssm](#input\_ssm) | Parameter Store paths and tag scopes available to compute-provider bootstrap resources.<br/><br/>- `paths.root`: Root Parameter Store path for the runner configuration.<br/>- `paths.tokens`: Path segment used for registration tokens and just-in-time configuration.<br/>- `paths.config`: Path segment used for persistent runner and provider configuration. MicroVM control-plane metadata is stored under its `microvm-metadata` child prefix.<br/>- `tags`: Shared SSM tags that override module-level `tags`.<br/>- `parameters.tags`: Parameter-specific tags that override module-level and shared SSM tags. | <pre>object({<br/> paths = object({<br/> root = string<br/> tokens = string<br/> config = string<br/> })<br/> tags = optional(map(string), {})<br/> parameters = optional(object({<br/> tags = optional(map(string), {})<br/> }), {})<br/> })</pre> | n/a | yes |
5656
| <a name="input_tags"></a> [tags](#input\_tags) | Base tags available to taggable compute-provider resources. Provider-specific tags override this map within their documented scopes. | `map(string)` | `{}` | no |
5757

modules/compute-providers/aws/microvm/control-plane.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ data "aws_iam_policy_document" "scale_down" {
7070
}
7171

7272
locals {
73-
microvm_metadata_ssm_path = "${trimsuffix(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.config, "/")}/microvm-metadata"
73+
microvm_metadata_ssm_path = "/${trim(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.config, "/")}/microvm-metadata"
7474
microvm_metadata_path_arn = "${local.ssm_parameter_arn_prefix}${local.microvm_metadata_ssm_path}"
7575
microvm_metadata_parameter_arn = "${local.microvm_metadata_path_arn}/*"
7676
microvm_metadata_tags = {
@@ -80,14 +80,14 @@ locals {
8080
local.provider_tags["Name"],
8181
)
8282
"ghr:environment" = var.prefix
83-
"ghr:ssm_config_path" = "${trimsuffix(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.config, "/")}"
83+
"ghr:ssm_config_path" = "/${trim(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.config, "/")}"
8484
"ghr:runner_name_prefix" = var.runner.name_prefix
8585
}
8686
microvm_image_resource_arns = coalesce(
8787
var.config.iam.resource_arns.images,
8888
[var.config.image_arn],
8989
)
90-
runner_jit_ssm_path = "${trimsuffix(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.tokens, "/")}"
90+
runner_jit_ssm_path = "/${trim(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.tokens, "/")}"
9191

9292
microvm_environment_variables = merge(var.config.environment_variables, {
9393
MICROVM_EGRESS_NETWORK_CONNECTORS = length(var.config.egress_network_connectors) == 0 ? "" : jsonencode(var.config.egress_network_connectors)
@@ -97,6 +97,7 @@ locals {
9797
MICROVM_INGRESS_NETWORK_CONNECTORS = length(var.config.ingress_network_connectors) == 0 ? "" : jsonencode(var.config.ingress_network_connectors)
9898
MICROVM_LOG_GROUP = aws_cloudwatch_log_group.runtime.name
9999
MICROVM_METADATA_SSM_PATH = local.microvm_metadata_ssm_path
100+
MICROVM_RUNNER_CONFIG_SSM_ARN = local.ssm_config_arn
100101
MICROVM_METADATA_TAGS = jsonencode([
101102
for key, value in local.microvm_metadata_tags : {
102103
Key = key

modules/compute-providers/aws/microvm/policies-runner.tf

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ data "aws_caller_identity" "current" {}
22

33
locals {
44
ssm_parameter_arn_prefix = "arn:${var.aws_partition}:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter"
5-
runner_token_path_arn = "${local.ssm_parameter_arn_prefix}${var.ssm.paths.root}/${var.ssm.paths.tokens}/*"
6-
ssm_config_arn = "${local.ssm_parameter_arn_prefix}${var.ssm.paths.root}/${var.ssm.paths.config}"
7-
5+
runner_token_path_arn = "${local.ssm_parameter_arn_prefix}/${trim(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.tokens, "/")}/*"
6+
ssm_config_arn = "${local.ssm_parameter_arn_prefix}/${trim(var.ssm.paths.root, "/")}/${trim(var.ssm.paths.config, "/")}"
87

98
runner_inline_policies = {
109
ssm_jit = {
@@ -35,16 +34,9 @@ data "aws_iam_policy_document" "runner_ssm_jit" {
3534

3635
data "aws_iam_policy_document" "runner_metadata" {
3736
statement {
38-
effect = "Allow"
39-
actions = [
40-
"ssm:GetParameter",
41-
"ssm:GetParameters",
42-
"ssm:GetParametersByPath",
43-
]
44-
resources = [
45-
local.ssm_config_arn,
46-
"${local.ssm_config_arn}/*",
47-
]
37+
effect = "Allow"
38+
actions = ["ssm:GetParameter"]
39+
resources = [local.microvm_metadata_parameter_arn]
4840
}
4941
}
5042

modules/compute-providers/aws/microvm/tests/provider.tftest.hcl

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ variables {
3737
"arn:aws:lambda:eu-west-1:123456789012:network-connector:egress",
3838
]
3939
environment_variables = {
40-
MICROVM_CLUSTER = "runner-cluster"
41-
MICROVM_IMAGE_ARN = "caller-cannot-override-provider-contract"
42-
MICROVM_METADATA_SSM_PATH = "/caller/cannot/override/provider-contract"
43-
MICROVM_METADATA_TAGS = "caller-cannot-override-provider-contract"
40+
MICROVM_CLUSTER = "runner-cluster"
41+
MICROVM_IMAGE_ARN = "caller-cannot-override-provider-contract"
42+
MICROVM_METADATA_SSM_PATH = "/caller/cannot/override/provider-contract"
43+
MICROVM_METADATA_TAGS = "caller-cannot-override-provider-contract"
44+
MICROVM_RUNNER_CONFIG_SSM_ARN = "caller-cannot-override-provider-contract"
4445
}
4546
}
4647

@@ -111,6 +112,7 @@ run "exposes_microvm_control_plane_contract" {
111112
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_EGRESS_NETWORK_CONNECTORS"])[0] == "arn:aws:lambda:eu-west-1:123456789012:network-connector:egress"
112113
&& output.provider.environment_variables.scale_up["MICROVM_LOG_GROUP"] == "/github-self-hosted-runners/microvm-test/microvm"
113114
&& output.provider.environment_variables.scale_up["MICROVM_METADATA_SSM_PATH"] == "/github-action-runners/config/microvm-metadata"
115+
&& output.provider.environment_variables.scale_up["MICROVM_RUNNER_CONFIG_SSM_ARN"] == "arn:aws:ssm:eu-west-1:123456789012:parameter/github-action-runners/config"
114116
&& tomap({
115117
for tag in jsondecode(output.provider.environment_variables.scale_up["MICROVM_METADATA_TAGS"]) :
116118
tag.Key => tag.Value
@@ -136,6 +138,7 @@ run "exposes_microvm_control_plane_contract" {
136138
"MICROVM_LOG_GROUP",
137139
"MICROVM_METADATA_SSM_PATH",
138140
"MICROVM_METADATA_TAGS",
141+
"MICROVM_RUNNER_CONFIG_SSM_ARN",
139142
])
140143
&& output.provider.environment_variables.scale_up == output.provider.environment_variables.scale_down
141144
&& output.provider.environment_variables.scale_up == output.provider.environment_variables.pool
@@ -208,7 +211,8 @@ run "exposes_microvm_control_plane_contract" {
208211
assert {
209212
condition = (
210213
toset(keys(output.provider.policies)) == toset(["runner", "scale_up", "scale_down", "pool"])
211-
&& toset(keys(output.provider.policies.runner.inline_policies)) == toset(["runtime_logs", "ssm_jit"])
214+
&& toset(keys(output.provider.policies.runner.inline_policies)) == toset(["runner_metadata", "runtime_logs", "ssm_jit"])
215+
&& output.provider.policies.runner.inline_policies.runner_metadata.name == "runner-microvm-metadata"
212216
&& output.provider.policies.runner.inline_policies.ssm_jit.name == "runner-microvm-ssm-jit"
213217
&& output.provider.policies.runner.inline_policies.runtime_logs.name == "runner-microvm-runtime-logs"
214218
&& output.provider.policies.runner.managed_policy_arns["readonly"] == "arn:aws:iam::aws:policy/ReadOnlyAccess"
@@ -222,11 +226,13 @@ run "exposes_microvm_control_plane_contract" {
222226
condition = (
223227
data.aws_iam_policy_document.runner_ssm_jit.statement[0].actions == toset(["ssm:DeleteParameter", "ssm:GetParameter"])
224228
&& data.aws_iam_policy_document.runner_ssm_jit.statement[0].resources == toset(["arn:aws:ssm:eu-west-1:123456789012:parameter/github-action-runners/tokens/*"])
229+
&& data.aws_iam_policy_document.runner_metadata.statement[0].actions == toset(["ssm:GetParameter"])
230+
&& data.aws_iam_policy_document.runner_metadata.statement[0].resources == toset(["${output.provider.environment_variables.scale_up["MICROVM_RUNNER_CONFIG_SSM_ARN"]}/microvm-metadata/*"])
225231
&& length(data.aws_iam_policy_document.runner_runtime_logs.statement) == 1
226232
&& data.aws_iam_policy_document.runner_runtime_logs.statement[0].actions == toset(["logs:CreateLogStream", "logs:PutLogEvents"])
227233
&& data.aws_iam_policy_document.runner_runtime_logs.statement[0].resources == toset(["arn:aws:logs:eu-west-1:123456789012:log-group:/github-self-hosted-runners/microvm-test/microvm:*"])
228234
)
229-
error_message = "Managed MicroVM runners must receive lane-token value, tag, and deletion access plus stream-write permissions on the provider-managed runtime log group."
235+
error_message = "Managed MicroVM runners must receive lane-scoped metadata read access, lane-token value and deletion access, plus stream-write permissions on the provider-managed runtime log group."
230236
}
231237

232238
assert {
@@ -257,6 +263,30 @@ run "exposes_microvm_control_plane_contract" {
257263
}
258264
}
259265

266+
run "normalizes_ssm_paths_and_arns" {
267+
command = plan
268+
269+
variables {
270+
ssm = {
271+
paths = {
272+
root = "/github-action-runners/"
273+
tokens = "/tokens/"
274+
config = "/config/"
275+
}
276+
}
277+
}
278+
279+
assert {
280+
condition = (
281+
output.provider.environment_variables.scale_up["MICROVM_METADATA_SSM_PATH"] == "/github-action-runners/config/microvm-metadata"
282+
&& output.provider.environment_variables.scale_up["MICROVM_RUNNER_CONFIG_SSM_ARN"] == "arn:aws:ssm:eu-west-1:123456789012:parameter/github-action-runners/config"
283+
&& data.aws_iam_policy_document.runner_ssm_jit.statement[0].resources == toset(["arn:aws:ssm:eu-west-1:123456789012:parameter/github-action-runners/tokens/*"])
284+
&& data.aws_iam_policy_document.runner_metadata.statement[0].resources == toset(["arn:aws:ssm:eu-west-1:123456789012:parameter/github-action-runners/config/microvm-metadata/*"])
285+
)
286+
error_message = "The MicroVM provider must normalize SSM path segments before exposing hook values or IAM resources."
287+
}
288+
}
289+
260290
run "accepts_external_runner_role_and_policy_overrides" {
261291
command = plan
262292

@@ -330,7 +360,8 @@ run "accepts_external_runner_role_and_policy_overrides" {
330360

331361
assert {
332362
condition = (
333-
toset(keys(output.provider.policies.runner.inline_policies)) == toset(["runtime_logs", "ssm_jit"])
363+
toset(keys(output.provider.policies.runner.inline_policies)) == toset(["runner_metadata", "runtime_logs", "ssm_jit"])
364+
&& data.aws_iam_policy_document.runner_metadata.statement[0].resources == toset(["${output.provider.environment_variables.scale_up["MICROVM_RUNNER_CONFIG_SSM_ARN"]}/microvm-metadata/*"])
334365
&& length(data.aws_iam_policy_document.runner_runtime_logs.statement) == 1
335366
&& data.aws_iam_policy_document.runner_runtime_logs.statement[0].resources == toset(["arn:aws:logs:eu-west-1:123456789012:log-group:/github-self-hosted-runners/microvm-test/microvm:*"])
336367
)

modules/compute-providers/aws/microvm/variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ variable "runner" {
8282
- `hooks.job_completed`: Script installed as the runner job-completed hook.
8383
- `iam.role.arn`: Resolved runner-role ARN used as the MicroVM execution role and referenced by provider policies.
8484
- `iam.role.name`: Resolved runner-role name used by provider resources.
85-
- `iam.role.managed`: Whether runner-config manages the resolved runner role. Callers own an external role and must grant it `ssm:GetParameter` and `ssm:DeleteParameter` on the lane token path plus `logs:CreateLogStream` and `logs:PutLogEvents` on the provider-managed runtime log group.
85+
- `iam.role.managed`: Whether runner-config manages the resolved runner role. Callers own an external role and must grant it `ssm:GetParameter` on the lane metadata child prefix, `ssm:GetParameter` and `ssm:DeleteParameter` on the lane token path, plus `logs:CreateLogStream` and `logs:PutLogEvents` on the provider-managed runtime log group.
8686
- `iam.managed_policy_arns`: Common managed-policy ARNs returned with the provider-specific runner policies for attachment by runner-config.
8787
- `iam.path`: IAM path available to provider-managed IAM resources. Null derives the path from `prefix`.
8888
EOT

0 commit comments

Comments
 (0)