Skip to content

Commit

Permalink
feat: add workload identity, add service account flag (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
Trojan295 authored Oct 9, 2024
1 parent 250a848 commit 319350e
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 62 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Lint

on:
pull_request:
branches:
- main

jobs:
tflint:
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ubuntu-latest]

steps:
- uses: actions/checkout@v2
name: Checkout source code

- uses: actions/cache@v2
name: Cache plugin dir
with:
path: ~/.tflint.d/plugins
key: ${{ matrix.os }}-tflint-${{ hashFiles('.tflint.hcl') }}

- uses: terraform-linters/setup-tflint@v1
name: Setup TFLint
with:
tflint_version: v0.53.0

- name: Show version
run: tflint --version

- name: Init TFLint
run: tflint --init

- name: Run TFLint
run: tflint -f compact
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,26 @@ No modules.
| [google_project_iam_member.project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.scoped_project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.scoped_service_account_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.workload_identity_project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.workload_identity_scoped_project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.workload_identity_scoped_service_account_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_service_account.castai_service_account](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource |
| [google_service_account_key.castai_key](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_key) | resource |
| [castai_gke_user_policies.gke](https://registry.terraform.io/providers/castai/castai/latest/docs/data-sources/gke_user_policies) | data source |
| [google_project.project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_cloud_proxy_service_account_name"></a> [cloud\_proxy\_service\_account\_name](#input\_cloud\_proxy\_service\_account\_name) | Name of the cloud-proxy Kubernetes Service Account | `string` | `"castai-cloud-proxy"` | no |
| <a name="input_cloud_proxy_service_account_namespace"></a> [cloud\_proxy\_service\_account\_namespace](#input\_cloud\_proxy\_service\_account\_namespace) | Namespace of the cloud-proxy Kubernetes Service Account | `string` | `"castai-agent"` | no |
| <a name="input_compute_manager_project_ids"></a> [compute\_manager\_project\_ids](#input\_compute\_manager\_project\_ids) | Projects list for shared sole tenancy nodes | `list(string)` | `[]` | no |
| <a name="input_create_service_account"></a> [create\_service\_account](#input\_create\_service\_account) | Whether an Service Account with private key should be created | `bool` | `true` | no |
| <a name="input_gke_cluster_name"></a> [gke\_cluster\_name](#input\_gke\_cluster\_name) | GKE cluster name for which to create IAM roles | `string` | n/a | yes |
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | The project id from GCP | `string` | n/a | yes |
| <a name="input_service_accounts_unique_ids"></a> [service\_accounts\_unique\_ids](#input\_service\_accounts\_unique\_ids) | Service Accounts' unique IDs used by node pools in the cluster | `list(string)` | `[]` | no |
| <a name="input_setup_cloud_proxy_workload_identity"></a> [setup\_cloud\_proxy\_workload\_identity](#input\_setup\_cloud\_proxy\_workload\_identity) | Whether the workload identity for castai-cloud-proxy should be setup | `bool` | `false` | no |
| <a name="input_workload_identity_namespace"></a> [workload\_identity\_namespace](#input\_workload\_identity\_namespace) | Override workload identity namespace, default is <project-id>.svc.id.goog | `string` | `""` | no |

## Outputs

Expand Down
63 changes: 5 additions & 58 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
## IAM user required for CAST.AI

locals {
service_account_id = "castai-gke-tf-${substr(sha1(var.gke_cluster_name), 0, 8)}"
service_account_email = "${local.service_account_id}@${var.project_id}.iam.gserviceaccount.com"
custom_role_id = "castai.gkeAccess.${substr(sha1(var.gke_cluster_name), 0, 8)}.tf"
condition_expression = join("||", formatlist("resource.name.startsWith(\"projects/-/serviceAccounts/%s\")", var.service_accounts_unique_ids))
default_permissions = ["roles/iam.serviceAccountUser", "projects/${var.project_id}/roles/${local.custom_role_id}"]
scoped_permissions = ["projects/${var.project_id}/roles/${local.custom_role_id}"]
custom_role_id = "castai.gkeAccess.${substr(sha1(var.gke_cluster_name), 0, 8)}.tf"
condition_expression = join("||", formatlist("resource.name.startsWith(\"projects/-/serviceAccounts/%s\")", var.service_accounts_unique_ids))
default_permissions = ["roles/iam.serviceAccountUser", "projects/${var.project_id}/roles/${local.custom_role_id}"]
scoped_permissions = ["projects/${var.project_id}/roles/${local.custom_role_id}"]

compute_manager_project_ids = var.compute_manager_project_ids
}

resource "google_service_account" "castai_service_account" {
account_id = local.service_account_id
display_name = "Service account to manage ${var.gke_cluster_name} cluster via CAST"
project = var.project_id
}

data "castai_gke_user_policies" "gke" {}

data "google_project" "project" {
project_id = var.project_id
}

resource "google_project_iam_custom_role" "castai_role" {
role_id = local.custom_role_id
title = "Role to manage GKE cluster via CAST AI"
Expand All @@ -32,43 +20,6 @@ resource "google_project_iam_custom_role" "castai_role" {
stage = "GA"
}

resource "google_project_iam_member" "project" {
for_each = (
length(var.service_accounts_unique_ids) == 0 ?
{ for permission in local.default_permissions : permission => permission } :
{}
)

project = var.project_id
role = each.key
member = "serviceAccount:${local.service_account_email}"
}

resource "google_project_iam_member" "scoped_project" {
for_each = (
length(var.service_accounts_unique_ids) > 0 ?
{ for permission in local.scoped_permissions : permission => permission } :
{}
)
project = var.project_id
role = each.key
member = "serviceAccount:${local.service_account_email}"
}

resource "google_project_iam_member" "scoped_service_account_user" {
count = length(var.service_accounts_unique_ids) > 0 ? 1 : 0
project = var.project_id

role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${local.service_account_email}"

condition {
title = "iam_condition"
description = "IAM condition with limited scope"
expression = local.condition_expression
}
}

resource "google_project_iam_custom_role" "compute_manager_role" {
for_each = toset(local.compute_manager_project_ids)

Expand All @@ -86,10 +37,6 @@ resource "google_project_iam_binding" "compute_manager_binding" {

project = each.key
role = "projects/${each.key}/roles/castai.gkeAccess.${substr(sha1(each.key), 0, 8)}.tf"
members = ["serviceAccount:${local.service_account_email}"]
members = compact(["serviceAccount:${local.service_account_email}", var.setup_cloud_proxy_workload_identity ? local.workload_identity_sa : null])
}

resource "google_service_account_key" "castai_key" {
service_account_id = google_service_account.castai_service_account.name
public_key_type = "TYPE_X509_PEM_FILE"
}
7 changes: 4 additions & 3 deletions output.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
output "private_key" {
value = base64decode(google_service_account_key.castai_key.private_key)
value = var.create_service_account ? base64decode(google_service_account_key.castai_key[0].private_key) : ""
sensitive = true

depends_on = [
Expand All @@ -12,9 +12,10 @@ output "private_key" {
}

output "service_account_id" {
value = google_service_account.castai_service_account.account_id
value = var.create_service_account ? google_service_account.castai_service_account[0].account_id : ""
}

output "service_account_email" {
value = google_service_account.castai_service_account.email
value = var.create_service_account ? google_service_account.castai_service_account[0].email : ""
}

64 changes: 64 additions & 0 deletions service_account.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
moved {
from = google_service_account.castai_service_account
to = google_service_account.castai_service_account[0]
}

moved {
from = google_service_account_key.castai_key
to = google_service_account_key.castai_key[0]
}

locals {
service_account_id = "castai-gke-tf-${substr(sha1(var.gke_cluster_name), 0, 8)}"
service_account_email = "${local.service_account_id}@${var.project_id}.iam.gserviceaccount.com"
}

resource "google_service_account" "castai_service_account" {
count = var.create_service_account ? 1 : 0
account_id = local.service_account_id
display_name = "Service account to manage ${var.gke_cluster_name} cluster via CAST"
project = var.project_id
}

resource "google_service_account_key" "castai_key" {
count = var.create_service_account ? 1 : 0
service_account_id = google_service_account.castai_service_account[0].name
public_key_type = "TYPE_X509_PEM_FILE"
}

resource "google_project_iam_member" "project" {
for_each = (
var.create_service_account && length(var.service_accounts_unique_ids) == 0 ?
{ for permission in local.default_permissions : permission => permission } :
{}
)

project = var.project_id
role = each.key
member = "serviceAccount:${local.service_account_email}"
}

resource "google_project_iam_member" "scoped_project" {
for_each = (
var.create_service_account && length(var.service_accounts_unique_ids) > 0 ?
{ for permission in local.scoped_permissions : permission => permission } :
{}
)
project = var.project_id
role = each.key
member = "serviceAccount:${local.service_account_email}"
}

resource "google_project_iam_member" "scoped_service_account_user" {
count = var.create_service_account && length(var.service_accounts_unique_ids) > 0 ? 1 : 0
project = var.project_id

role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${local.service_account_email}"

condition {
title = "iam_condition"
description = "IAM condition with limited scope"
expression = local.condition_expression
}
}
31 changes: 31 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,34 @@ variable "compute_manager_project_ids" {
description = "Projects list for shared sole tenancy nodes"
default = []
}

variable "create_service_account" {
type = bool
description = "Whether an Service Account with private key should be created"
default = true
}

variable "setup_cloud_proxy_workload_identity" {
type = bool
description = "Whether the workload identity for castai-cloud-proxy should be setup"
default = false
}

variable "workload_identity_namespace" {
type = string
description = "Override workload identity namespace, default is <project-id>.svc.id.goog"
default = ""
}

variable "cloud_proxy_service_account_namespace" {
type = string
description = "Namespace of the cloud-proxy Kubernetes Service Account"
default = "castai-agent"
}


variable "cloud_proxy_service_account_name" {
type = string
description = "Name of the cloud-proxy Kubernetes Service Account"
default = "castai-cloud-proxy"
}
42 changes: 42 additions & 0 deletions workload_identity.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
locals {
workload_identity_namespace = var.workload_identity_namespace != "" ? var.workload_identity_namespace : "${var.project_id}.svc.id.goog"
workload_identity_sa = "serviceAccount:${local.workload_identity_namespace}[${var.cloud_proxy_service_account_namespace}/${var.cloud_proxy_service_account_name}]"
}

resource "google_project_iam_member" "workload_identity_project" {
for_each = (
var.setup_cloud_proxy_workload_identity && length(var.service_accounts_unique_ids) == 0 ?
{ for permission in local.default_permissions : permission => permission } :
{}
)

project = var.project_id
role = each.key
member = local.workload_identity_sa
}

resource "google_project_iam_member" "workload_identity_scoped_project" {
for_each = (
var.setup_cloud_proxy_workload_identity && length(var.service_accounts_unique_ids) > 0 ?
{ for permission in local.scoped_permissions : permission => permission } :
{}
)
project = var.project_id
role = each.key
member = local.workload_identity_sa
}

resource "google_project_iam_member" "workload_identity_scoped_service_account_user" {
count = var.setup_cloud_proxy_workload_identity && length(var.service_accounts_unique_ids) > 0 ? 1 : 0
project = var.project_id

role = "roles/iam.serviceAccountUser"
member = local.workload_identity_sa

condition {
title = "iam_condition"
description = "IAM condition with limited scope"
expression = local.condition_expression
}
}

0 comments on commit 319350e

Please sign in to comment.