Skip to content

Commit c008237

Browse files
tonybayvasapeabody
andauthored
feat(TPG>=6.39)!: Fleet app operator permissions custom roles (#2377)
Co-authored-by: Andrew Peabody <andrewpeabody@google.com>
1 parent 1e44733 commit c008237

File tree

9 files changed

+112
-30
lines changed

9 files changed

+112
-30
lines changed

examples/simple_fleet_app_operator_permissions/main.tf

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*/
1616

1717
locals {
18-
app_operator_id = "app-operator-id"
19-
app_operator_team = "app-operator-team"
20-
app_operator_role = "VIEW"
18+
app_operator_id = "app-operator-id"
19+
app_operator_team = "app-operator-team"
20+
app_operator_role = "VIEW"
21+
custom_app_operator_id = "custom-app-operator-id"
22+
custom_app_operator_role = "my-custom-role"
2123
}
2224

2325
# Create a Service Account, which can be used as an app operator.
@@ -27,12 +29,31 @@ resource "google_service_account" "service_account" {
2729
display_name = "Test App Operator Service Account"
2830
}
2931

32+
# Create another Service Account, which can be used as a custom role app operator.
33+
resource "google_service_account" "custom_service_account" {
34+
project = var.fleet_project_id
35+
account_id = local.custom_app_operator_id
36+
display_name = "Test App Operator Custom Role Service Account"
37+
}
38+
3039
# Create a Fleet Scope for the app operator's team.
3140
resource "google_gke_hub_scope" "scope" {
3241
project = var.fleet_project_id
3342
scope_id = local.app_operator_team
3443
}
3544

45+
# Allowlist custom roles for usage in Scope RBAC
46+
resource "google_gke_hub_feature" "rbacrolebindingactuation" {
47+
name = "rbacrolebindingactuation"
48+
location = "global"
49+
spec {
50+
rbacrolebindingactuation {
51+
allowed_custom_roles = [local.custom_app_operator_role]
52+
}
53+
}
54+
project = var.fleet_project_id
55+
}
56+
3657
# Grant permissions to the app operator to work with the Fleet Scope.
3758
module "permissions" {
3859
source = "terraform-google-modules/kubernetes-engine/google//modules/fleet-app-operator-permissions"
@@ -48,3 +69,19 @@ module "permissions" {
4869
]
4970
}
5071

72+
# Grant custom role permissions to the app operator to work with the Fleet Scope.
73+
module "custom_permissions" {
74+
source = "terraform-google-modules/kubernetes-engine/google//modules/fleet-app-operator-permissions"
75+
version = "~> 37.0"
76+
77+
fleet_project_id = var.fleet_project_id
78+
scope_id = google_gke_hub_scope.scope.scope_id
79+
users = ["${local.custom_app_operator_id}@${var.fleet_project_id}.iam.gserviceaccount.com"]
80+
custom_role = local.custom_app_operator_role
81+
82+
depends_on = [
83+
google_service_account.custom_service_account,
84+
google_gke_hub_feature.rbacrolebindingactuation,
85+
]
86+
}
87+

examples/simple_fleet_app_operator_permissions/versions.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ terraform {
2020
required_providers {
2121
google = {
2222
source = "hashicorp/google"
23-
version = ">= 4.81.0"
23+
version = ">= 6.39.0"
2424
}
2525
google-beta = {
2626
source = "hashicorp/google-beta"
27-
version = ">= 4.81.0"
27+
version = ">= 6.39.0"
2828
}
2929
}
3030
}

modules/fleet-app-operator-permissions/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Terrafrom Module for Fleet App Operator Permissions
1+
# Terraform Module for Fleet App Operator Permissions
22

33
This module bundles different permissions (IAM and RBAC Role Bindings) required for [Fleet team management](https://cloud.google.com/kubernetes-engine/fleet-management/docs/team-management). A platform admin can use this module to set up permissions for an app operator (user or group) in a team--including usage of Fleet Scopes, Connect Gateway, logging, and metrics--based on predefined roles (VIEW, EDIT, ADMIN).
44

@@ -14,6 +14,17 @@ module "fleet_app_operator_permissions" {
1414
groups = ["people@company.com"]
1515
role = "EDIT"
1616
}
17+
18+
Example:
19+
module "fleet_app_operator_permissions" {
20+
source = "terraform-google-modules/kubernetes-engine/google//modules/fleet-app-operator-permissions"
21+
22+
fleet_project_id = "my-project-id"
23+
scope_id = "frontend-team"
24+
users = ["person1@company.com", "person2@company.com"]
25+
groups = ["people@company.com"]
26+
custom_role = "my-custom-role"
27+
}
1728
```
1829

1930
To deploy this config, run:
@@ -28,9 +39,10 @@ To deploy this config, run:
2839

2940
| Name | Description | Type | Default | Required |
3041
|------|-------------|------|---------|:--------:|
42+
| custom\_role | The principal's role for the Fleet Scope which is a custom Kubernetes ClusterRole. Either a predefined role or a custom role should be set | `string` | `null` | no |
3143
| fleet\_project\_id | The project to which the Fleet belongs. | `string` | n/a | yes |
3244
| groups | The list of app operator group principals, e.g., `people@google.com`, `principalSet://iam.googleapis.com/locations/global/workforcePools/my-pool/group/people`. | `list(string)` | `[]` | no |
33-
| role | The principals role for the Fleet Scope (`VIEW`/`EDIT`/`ADMIN`). | `string` | n/a | yes |
45+
| role | The principal's predefined role for the Fleet Scope (`VIEW`/`EDIT`/`ADMIN`). Either a predefined role or a custom role should be set | `string` | `null` | no |
3446
| scope\_id | The scope for which IAM and RBAC role bindings are created. | `string` | n/a | yes |
3547
| users | The list of app operator user principals, e.g., `person@google.com`, `principal://iam.googleapis.com/locations/global/workforcePools/my-pool/subject/person`, `serviceAccount:my-service-account@my-project.iam.gserviceaccount.com`. | `list(string)` | `[]` | no |
3648

modules/fleet-app-operator-permissions/main.tf

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ locals {
2727
))]
2828

2929
project_level_scope_role = {
30-
"VIEW" = "roles/gkehub.scopeViewerProjectLevel"
31-
"EDIT" = "roles/gkehub.scopeEditorProjectLevel"
32-
"ADMIN" = "roles/gkehub.scopeEditorProjectLevel" # Same as EDIT
30+
"VIEW" = "roles/gkehub.scopeViewerProjectLevel"
31+
"EDIT" = "roles/gkehub.scopeEditorProjectLevel"
32+
"ADMIN" = "roles/gkehub.scopeEditorProjectLevel" # Same as EDIT
33+
"CUSTOM" = "roles/gkehub.scopeEditorProjectLevel" # Same as EDIT
3334
}
3435

3536
resource_level_scope_role = {
36-
"VIEW" = "roles/gkehub.scopeViewer"
37-
"EDIT" = "roles/gkehub.scopeEditor"
38-
"ADMIN" = "roles/gkehub.scopeAdmin"
37+
"VIEW" = "roles/gkehub.scopeViewer"
38+
"EDIT" = "roles/gkehub.scopeEditor"
39+
"ADMIN" = "roles/gkehub.scopeAdmin"
40+
"CUSTOM" = "roles/gkehub.scopeViewer" # Same as VIEW
3941
}
4042
}
4143

@@ -54,15 +56,16 @@ resource "google_project_iam_member" "log_view_permissions" {
5456
resource "google_project_iam_member" "project_level_scope_permissions" {
5557
project = var.fleet_project_id
5658
for_each = toset(concat(local.user_principals, local.group_principals))
57-
role = local.project_level_scope_role[var.role]
59+
role = (var.custom_role != null ? local.project_level_scope_role["CUSTOM"] : local.project_level_scope_role[var.role])
5860
member = each.value
5961
}
6062

61-
resource "google_gke_hub_scope_iam_binding" "resource_level_scope_permissions" {
63+
resource "google_gke_hub_scope_iam_member" "resource_level_scope_permissions" {
6264
project = var.fleet_project_id
65+
for_each = toset(concat(local.user_principals, local.group_principals))
6366
scope_id = var.scope_id
64-
role = local.resource_level_scope_role[var.role]
65-
members = concat(local.user_principals, local.group_principals)
67+
role = (var.custom_role != null ? local.resource_level_scope_role["CUSTOM"] : local.resource_level_scope_role[var.role])
68+
member = each.value
6669
}
6770

6871
resource "random_id" "user_rand_suffix" {
@@ -77,6 +80,8 @@ resource "google_gke_hub_scope_rbac_role_binding" "scope_rbac_user_role_bindings
7780
scope_id = var.scope_id
7881
user = each.key
7982
role {
83+
# Setting both types of roles will return an error when creating the resource.
84+
custom_role = var.custom_role
8085
predefined_role = var.role
8186
}
8287
}
@@ -93,6 +98,8 @@ resource "google_gke_hub_scope_rbac_role_binding" "scope_rbac_group_role_binding
9398
scope_id = var.scope_id
9499
group = each.key
95100
role {
101+
# Setting both types of roles will return an error when creating the resource.
102+
custom_role = var.custom_role
96103
predefined_role = var.role
97104
}
98105
}

modules/fleet-app-operator-permissions/metadata.display.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ metadata:
2020
config.kubernetes.io/local-config: "true"
2121
spec:
2222
info:
23-
title: Terrafrom Module for Fleet App Operator Permissions
23+
title: Terraform Module for Fleet App Operator Permissions
2424
source:
2525
repo: https://github.com/terraform-google-modules/terraform-google-kubernetes-engine.git
2626
sourceType: git
2727
dir: /modules/fleet-app-operator-permissions
2828
ui:
2929
input:
3030
variables:
31+
custom_role:
32+
name: custom_role
33+
title: Custom Role
3134
fleet_project_id:
3235
name: fleet_project_id
3336
title: Fleet Project Id

modules/fleet-app-operator-permissions/metadata.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ metadata:
2020
config.kubernetes.io/local-config: "true"
2121
spec:
2222
info:
23-
title: Terrafrom Module for Fleet App Operator Permissions
23+
title: Terraform Module for Fleet App Operator Permissions
2424
source:
2525
repo: https://github.com/terraform-google-modules/terraform-google-kubernetes-engine.git
2626
sourceType: git
@@ -139,9 +139,11 @@ spec:
139139
varType: list(string)
140140
defaultValue: []
141141
- name: role
142-
description: The principals role for the Fleet Scope (`VIEW`/`EDIT`/`ADMIN`).
142+
description: The principal's predefined role for the Fleet Scope (`VIEW`/`EDIT`/`ADMIN`). Either a predefined role or a custom role should be set
143+
varType: string
144+
- name: custom_role
145+
description: The principal's role for the Fleet Scope which is a custom Kubernetes ClusterRole. Either a predefined role or a custom role should be set
143146
varType: string
144-
required: true
145147
outputs:
146148
- name: fleet_project_id
147149
description: The project to which the Fleet belongs.

modules/fleet-app-operator-permissions/variables.tf

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,18 @@ variable "groups" {
3737
}
3838

3939
variable "role" {
40-
description = "The principals role for the Fleet Scope (`VIEW`/`EDIT`/`ADMIN`)."
40+
description = "The principal's predefined role for the Fleet Scope (`VIEW`/`EDIT`/`ADMIN`). Either a predefined role or a custom role should be set"
4141
type = string
4242
validation {
43-
condition = contains(["VIEW", "EDIT", "ADMIN"], var.role)
44-
error_message = "Allowed values for role are VIEW, EDIT, or ADMIN."
43+
condition = var.role == null || contains(["VIEW", "EDIT", "ADMIN"], var.role)
44+
error_message = "Allowed values for role are VIEW, EDIT, ADMIN, or null."
4545
}
46+
default = null
47+
}
48+
49+
variable "custom_role" {
50+
description = "The principal's role for the Fleet Scope which is a custom Kubernetes ClusterRole. Either a predefined role or a custom role should be set"
51+
type = string
52+
default = null
4653
}
4754

modules/fleet-app-operator-permissions/versions.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ terraform {
2020
required_providers {
2121
google = {
2222
source = "hashicorp/google"
23-
version = ">= 4.81.0"
23+
version = ">= 6.39.0"
2424
}
2525
google-beta = {
2626
source = "hashicorp/google-beta"
27-
version = ">= 4.81.0"
27+
version = ">= 6.39.0"
2828
}
2929
random = {
3030
source = "hashicorp/random"

test/integration/simple_fleet_app_operator_permissions/simple_fleet_app_operator_permissions_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,37 @@ func TestSimpleFleetAppOperatorPermissions(t *testing.T) {
3939
appOperatorPrincipal := fmt.Sprintf("serviceAccount:%s", appOperatorEmail)
4040
scopeLevelRole := "roles/gkehub.scopeViewer"
4141
projectLevelRole := "roles/gkehub.scopeViewerProjectLevel"
42+
customAppOperatorEmail := fmt.Sprintf("custom-app-operator-id@%s.iam.gserviceaccount.com", projectId)
43+
customAppOperatorPrincipal := fmt.Sprintf("serviceAccount:%s", customAppOperatorEmail)
44+
customScopeLevelRole := "roles/gkehub.scopeViewer"
45+
customProjectLevelRole := "roles/gkehub.scopeEditorProjectLevel"
4246
logViewRole := "roles/logging.viewAccessor"
4347
logViewContainerBucket := fmt.Sprintf("projects/%s/locations/global/buckets/fleet-o11y-scope-%s/views/fleet-o11y-scope-%s-k8s_container", projectId, scopeId, scopeId)
4448
logViewPodBucket := fmt.Sprintf("projects/%s/locations/global/buckets/fleet-o11y-scope-%s/views/fleet-o11y-scope-%s-k8s_pod", projectId, scopeId, scopeId)
49+
filterFormat := "\"bindings.members:%s\""
50+
flattenOpt := "bindings[].members"
4551

4652
scopeRrbList := gcloud.Runf(t, "container fleet scopes rbacrolebindings list --scope %s --project %s", scopeId, projectId).String()
4753
assert.Equal(strings.Contains(scopeRrbList, appOperatorEmail), true, "app operator email should be in the list of Scope RBAC Role Bindings")
54+
assert.Equal(strings.Contains(scopeRrbList, customAppOperatorEmail), true, "custom app operator email should be in the list of Scope RBAC Role Bindings")
4855

49-
scopeIam := gcloud.Runf(t, "container fleet scopes get-iam-policy %s --project %s", scopeId, projectId).String()
50-
assert.Equal(strings.Contains(scopeIam, appOperatorPrincipal), true, "app operator principal should be in the Scope IAM policy")
56+
scopeIam := gcloud.Runf(t, "container fleet scopes get-iam-policy %s --project %s --filter %s", scopeId, projectId, fmt.Sprintf(filterFormat, appOperatorPrincipal)).String()
5157
assert.Equal(strings.Contains(scopeIam, scopeLevelRole), true, "app operator Scope role should be in the Scope IAM policy")
5258

53-
projectIam := gcloud.Runf(t, "projects get-iam-policy %s", projectId).String()
54-
assert.Equal(strings.Contains(projectIam, appOperatorPrincipal), true, "app operator principal should be in the project IAM policy")
59+
customScopeIam := gcloud.Runf(t, "container fleet scopes get-iam-policy %s --project %s --filter %s", scopeId, projectId, fmt.Sprintf(filterFormat, customAppOperatorPrincipal)).String()
60+
assert.Equal(strings.Contains(customScopeIam, customScopeLevelRole), true, "custom app operator Scope role should be in the Scope IAM policy")
61+
62+
projectIam := gcloud.Runf(t, "projects get-iam-policy %s --filter %s --flatten %s", projectId, fmt.Sprintf(filterFormat, appOperatorPrincipal), flattenOpt).String()
5563
assert.Equal(strings.Contains(projectIam, projectLevelRole), true, "app operator Scope role should be in the project IAM policy")
5664
assert.Equal(strings.Contains(projectIam, logViewRole), true, "app operator log view role should be in the project IAM policy")
5765
assert.Equal(strings.Contains(projectIam, logViewContainerBucket), true, "app operator log view container bucket should be in the project IAM policy")
5866
assert.Equal(strings.Contains(projectIam, logViewPodBucket), true, "app operator log view pod bucket should be in the project IAM policy")
67+
68+
customProjectIam := gcloud.Runf(t, "projects get-iam-policy %s --filter %s --flatten %s", projectId, fmt.Sprintf(filterFormat, customAppOperatorPrincipal), flattenOpt).String()
69+
assert.Equal(strings.Contains(customProjectIam, customProjectLevelRole), true, "custom app operator Scope role should be in the project IAM policy")
70+
assert.Equal(strings.Contains(customProjectIam, logViewRole), true, "custom app operator log view role should be in the project IAM policy")
71+
assert.Equal(strings.Contains(customProjectIam, logViewContainerBucket), true, "custom app operator log view container bucket should be in the project IAM policy")
72+
assert.Equal(strings.Contains(customProjectIam, logViewPodBucket), true, "custom app operator log view pod bucket should be in the project IAM policy")
5973
})
6074

6175
appOppT.Test()

0 commit comments

Comments
 (0)