Skip to content

Commit 2cec389

Browse files
committed
Allow skipping Google Workspace SA and API provisioning via connector settings.
Expose google_workspace_provision_service_accounts and google_workspace_enable_apis in google_workspace_connector_settings so customers can manage those GCP steps outside Terraform, with matching TODO outputs and a state move for existing service accounts.
1 parent 8498bbd commit 2cec389

11 files changed

Lines changed: 173 additions & 27 deletions

File tree

docs/sources/google-workspace/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,32 @@ While not recommended, it is possible to set up Google API clients without Terra
108108

109109
Then follow the steps in the next section to create the keys for the Oauth Clients.
110110

111+
If your organization's policies don't allow Terraform to manage some or all of these GCP resources, you can still use our Terraform modules for the rest of your deployment and disable the parts you must do manually via `google_workspace_connector_settings` in your `terraform.tfvars`:
112+
113+
```hcl
114+
google_workspace_connector_settings = {
115+
google_workspace_enable_apis = false
116+
google_workspace_provision_service_accounts = false
117+
google_workspace_provision_keys = false
118+
}
119+
```
120+
121+
When any of these are `false`, Terraform will skip creating the corresponding resources and instead emit TODO files (or `todos_1` outputs, if configured) with instructions to complete those steps outside of Terraform.
122+
111123
NOTE: if you are creating connections to multiple Google Workspace™ sources, you can use a single OAuth client and share it between all the proxy instances. You just need to authorize the entire superset of Oauth scopes required by those connnections for the OAuth Client via the Google Workspace™ Admin console.
112124

113125
### Provisioning API Keys without Terraform
114126

115127
If your organization's policies don't allow GCP service account keys to be managed via Terraform (or you lack the perms to do so), you can still use our Terraform modules to create the clients, and just add the following to your `terraform.tfvars` to disable provisioning of the keys:
116128

117129
```hcl
118-
google_workspace_provision_keys = false
130+
google_workspace_connector_settings = {
131+
google_workspace_provision_keys = false
132+
}
119133
```
120134

135+
The deprecated top-level variable `google_workspace_provision_keys` is still supported, but the map form above is preferred.
136+
121137
Then you can create the keys manually, and store them in your secrets manager of choice.
122138

123139
For each API client you need to:

infra/examples-dev/aws/google-workspace-variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,6 @@ locals {
8080

8181
variable "google_workspace_connector_settings" {
8282
type = map(any)
83-
description = "Map of configuration settings specifically for Google Workspace connectors (e.g. example users). Note that provider-controlling parameters (like GCP project IDs or impersonation SAs) remain top-level variables."
83+
description = "Map of configuration settings specifically for Google Workspace connectors. Supported keys: google_workspace_example_user, google_workspace_example_admin, google_workspace_provision_keys, google_workspace_key_rotation_days, google_workspace_provision_service_accounts, google_workspace_enable_apis. Provider-controlling parameters (like GCP project IDs or impersonation SAs) remain top-level variables."
8484
default = {}
8585
}

infra/examples-dev/gcp/google-workspace-variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,6 @@ locals {
8080

8181
variable "google_workspace_connector_settings" {
8282
type = map(any)
83-
description = "Map of configuration settings specifically for Google Workspace connectors (e.g. example users). Note that provider-controlling parameters (like GCP project IDs or impersonation SAs) remain top-level variables."
83+
description = "Map of configuration settings specifically for Google Workspace connectors. Supported keys: google_workspace_example_user, google_workspace_example_admin, google_workspace_provision_keys, google_workspace_key_rotation_days, google_workspace_provision_service_accounts, google_workspace_enable_apis. Provider-controlling parameters (like GCP project IDs or impersonation SAs) remain top-level variables."
8484
default = {}
8585
}

infra/modules/google-workspace-dwd-connection/main.tf

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,29 @@ locals {
1919
# TODO: md5 here is 32 chars of hex, so some risk of collision by truncating
2020
sa_account_id = length(local.padded_id) < 31 ? local.padded_id : substr(md5(local.padded_id), 0, 30)
2121

22-
instance_id = coalesce(var.instance_id, var.display_name)
22+
instance_id = coalesce(var.instance_id, var.display_name)
23+
expected_sa_email = "${local.sa_account_id}@${var.project_id}.iam.gserviceaccount.com"
24+
oauth_client_id = var.provision_service_account ? google_service_account.connector_sa[0].unique_id : "REPLACE_WITH_NUMERIC_CLIENT_ID_AFTER_CREATING_SERVICE_ACCOUNT"
25+
service_account_email_for_todo = var.provision_service_account ? google_service_account.connector_sa[0].email : local.expected_sa_email
2326
}
2427

2528
# service account to personify connector
29+
moved {
30+
from = google_service_account.connector_sa
31+
to = google_service_account.connector_sa[0]
32+
}
33+
2634
resource "google_service_account" "connector_sa" {
35+
count = var.provision_service_account ? 1 : 0
36+
2737
project = var.project_id
2838
account_id = local.sa_account_id
2939
display_name = var.display_name
3040
description = var.description
3141
}
3242

3343
resource "google_project_service" "apis_needed" {
34-
for_each = toset(var.apis_consumed)
44+
for_each = var.enable_apis ? toset(var.apis_consumed) : toset([])
3545

3646
service = each.key
3747
project = var.project_id
@@ -94,35 +104,42 @@ connection to will fail)
94104
as well as the ability of the proxy connection to impersonate it.
95105
EOT
96106

107+
manual_sa_todo_note = var.provision_service_account ? "" : <<-EOT
108+
109+
NOTE: Terraform did not provision this service account. After you create it manually, replace
110+
the placeholder client ID above with the numeric ID shown in the GCP console for
111+
`${local.expected_sa_email}`.
112+
EOT
113+
97114
todo_content = <<EOT
98115
Complete the following steps via the Google Workspace Admin console:
99116
1. Visit https://admin.google.com/ and navigate to "Security" --> "Access and Data Control" -->
100117
"API Controls", then find "Manage Domain Wide Delegation". Click "Add new".
101118
102-
2. Copy and paste client ID `${google_service_account.connector_sa.unique_id}` into the
119+
2. Copy and paste client ID `${local.oauth_client_id}` into the
103120
"Client ID" input in the popup. (this is the unique ID of the GCP service account with
104-
email `${google_service_account.connector_sa.email}`; you can (and should) verify its identity
105-
via the GCP console, with the project `${google_service_account.connector_sa.project}`, under:
121+
email `${local.service_account_email_for_todo}`; you can (and should) verify its identity
122+
via the GCP console, with the project `${var.project_id}`, under:
106123
107-
["IAM & Admin" --> "Service Accounts"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=${google_service_account.connector_sa.project}&supportedpurview=project)
124+
["IAM & Admin" --> "Service Accounts"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=${var.project_id}&supportedpurview=project)
108125
109126
This ensures you are granting domain-wide delegation to the correct service account, and
110127
mitigates the risk that these instructions were forged by a malicious actor.
111-
128+
${local.manual_sa_todo_note}
112129
Via the GCP console, you can also verify all extant keys for the service account, to ensure
113130
that there is exactly one, which should be held by the proxy. GCP provides log of key usage,
114131
creation, revocation, etc, which you can monitor to ensure that the key is being used only by
115132
the proxy, only for the data access you expect. If you ever suspect compromise, you may revoke
116-
the key from the GCP console at any time (NOTE: that proxy connection will be broken until your
117-
Terraform configuration is re-applied, to provision a new key).
133+
the key from the GCP console at any time (NOTE: that proxy connection will be broken until a new
134+
key is provisioned and stored in your secrets manager).
118135
119136
3. Copy and paste the following OAuth 2.0 scope string into the "Scopes" input:
120137
```
121138
${join(",", var.oauth_scopes_needed)}
122139
```
123140
124141
4. Authorize it. With this, your psoxy instance should be able to authenticate with Google as
125-
the GCP Service Account `${google_service_account.connector_sa.email}` and request data from
142+
the GCP Service Account `${local.service_account_email_for_todo}` and request data from
126143
Google as authorized by the OAuth scopes you granted.
127144
${local.google_workspace_admin_account_required ? local.google_workspace_service_account_setup : ""}
128145
EOT

infra/modules/google-workspace-dwd-connection/output.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ output "instance_id" {
33
}
44

55
output "service_account_id" {
6-
value = google_service_account.connector_sa.id
6+
value = var.provision_service_account ? google_service_account.connector_sa[0].id : "projects/${var.project_id}/serviceAccounts/${local.expected_sa_email}"
77
}
88

99
output "service_account_email" {
10-
value = google_service_account.connector_sa.email
10+
value = var.provision_service_account ? google_service_account.connector_sa[0].email : local.expected_sa_email
1111
}
1212

1313
output "service_account_numeric_id" {
14-
value = google_service_account.connector_sa.unique_id
14+
value = var.provision_service_account ? google_service_account.connector_sa[0].unique_id : null
15+
description = "OAuth client ID for domain-wide delegation; null if the service account is not provisioned by Terraform"
1516
}
1617

1718
output "next_todo_step" {

infra/modules/google-workspace-dwd-connection/variables.tf

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ variable "oauth_scopes_needed" {
3636
default = []
3737
}
3838

39+
variable "provision_service_account" {
40+
type = bool
41+
description = "whether to provision the GCP service account (OAuth client) via Terraform. If false, you must create it manually."
42+
default = true
43+
}
44+
45+
variable "enable_apis" {
46+
type = bool
47+
description = "whether to enable required GCP APIs via Terraform. If false, you must enable them manually."
48+
default = true
49+
}
50+
3951
variable "todos_as_local_files" {
4052
type = bool
4153
description = "whether to render TODOs as flat files"
@@ -47,4 +59,3 @@ variable "todo_step" {
4759
description = "of all todos, where does this one logically fall in sequence"
4860
default = 1
4961
}
50-

infra/modules/worklytics-connector-specs/variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,6 @@ variable "msft_365_connector_settings" {
237237

238238
variable "google_workspace_connector_settings" {
239239
type = map(any)
240-
description = "Map of configuration settings specifically for Google Workspace connectors (e.g. example users). Note that provider-controlling parameters (like GCP project IDs or impersonation SAs) remain top-level variables."
240+
description = "Map of configuration settings specifically for Google Workspace connectors. Supported keys: google_workspace_example_user, google_workspace_example_admin, google_workspace_provision_keys, google_workspace_key_rotation_days, google_workspace_provision_service_accounts, google_workspace_enable_apis. Provider-controlling parameters (like GCP project IDs or impersonation SAs) remain top-level variables."
241241
default = {}
242242
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
In the GCP console for `${gcp_project_id}` (or via `gcloud`), enable the following APIs required by the `${connector_id}` connector:
2+
3+
%{ for api in apis_consumed ~}
4+
- `${api}`
5+
%{ endfor ~}
6+
7+
Via the GCP console: navigate to "APIs & Services" --> "Library", search for each API above, and click "Enable".
8+
9+
Via gcloud (one command per API):
10+
%{ for api in apis_consumed ~}
11+
`gcloud services enable ${api} --project=${gcp_project_id}`
12+
%{ endfor ~}
13+
14+
See the page below for more information on provisioning Google Workspace connectors without Terraform:
15+
16+
https://docs.worklytics.co/psoxy/sources/google-workspace#provisioning-api-clients-without-terraform
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
In the GCP console for `${gcp_project_id}` (or via `gcloud`), create a service account to use as the OAuth client for the `${connector_id}` connector:
2+
3+
- **Account ID**: `${service_account_id}`
4+
- **Display name**: `${display_name}`
5+
- **Description**: `${description}`
6+
7+
The service account email should be `${expected_service_account_email}`.
8+
9+
Via the GCP console: navigate to "IAM & Admin" --> "Service Accounts" --> "Create Service Account", and use the values above.
10+
11+
Via gcloud:
12+
`gcloud iam service-accounts create ${service_account_id} --display-name="${display_name}" --description="${description}" --project=${gcp_project_id}`
13+
14+
After creating the service account, note its numeric **Client ID** (unique ID) from the service account details page. You will need it when completing domain-wide delegation setup.
15+
16+
See the page below for more information on provisioning Google Workspace connectors without Terraform:
17+
18+
https://docs.worklytics.co/psoxy/sources/google-workspace#provisioning-api-clients-without-terraform

infra/modules/worklytics-connectors-google-workspace/main.tf

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
locals {
2-
provision_gcp_sa_keys = try(var.google_workspace_connector_settings["google_workspace_provision_keys"], var.provision_gcp_sa_keys)
2+
provision_service_accounts = try(var.google_workspace_connector_settings["google_workspace_provision_service_accounts"], true)
3+
enable_apis = try(var.google_workspace_connector_settings["google_workspace_enable_apis"], true)
4+
provision_gcp_sa_keys = (
5+
local.provision_service_accounts
6+
? try(var.google_workspace_connector_settings["google_workspace_provision_keys"], var.provision_gcp_sa_keys)
7+
: false
8+
)
39
gcp_sa_key_rotation_days = try(var.google_workspace_connector_settings["google_workspace_key_rotation_days"], var.gcp_sa_key_rotation_days)
10+
11+
manual_steps_before_dwd = (local.enable_apis ? 0 : 1) + (local.provision_service_accounts ? 0 : 1)
12+
dwd_todo_step = var.todo_step + local.manual_steps_before_dwd
13+
api_todo_step = var.todo_step
14+
sa_todo_step = var.todo_step + (local.enable_apis ? 0 : 1)
15+
key_todo_step = local.dwd_todo_step + 1
416
}
517
terraform {
618
required_version = "~> 1.7"
@@ -46,24 +58,65 @@ module "google_workspace_connection" {
4658
description = "Google API OAuth Client for ${each.value.display_name}"
4759
apis_consumed = each.value.apis_consumed
4860
oauth_scopes_needed = each.value.oauth_scopes_needed
61+
provision_service_account = local.provision_service_accounts
62+
enable_apis = local.enable_apis
4963
todos_as_local_files = var.todos_as_local_files
50-
todo_step = var.todo_step
64+
todo_step = local.dwd_todo_step
5165
}
5266

5367
locals {
5468

69+
api_enable_todos = {
70+
for id, connection in module.google_workspace_connection :
71+
id => templatefile("${path.module}/gcp-api-enable-todo.tftpl", {
72+
gcp_project_id : var.gcp_project_id
73+
connector_id : id
74+
apis_consumed : module.worklytics_connector_specs.enabled_google_workspace_connectors[id].apis_consumed
75+
})
76+
}
77+
78+
sa_creation_todos = {
79+
for id, connection in module.google_workspace_connection :
80+
id => templatefile("${path.module}/gcp-sa-create-todo.tftpl", {
81+
gcp_project_id : var.gcp_project_id
82+
connector_id : id
83+
service_account_id : "${local.environment_id_prefix}${substr(id, 0, 30 - length(local.environment_id_prefix))}"
84+
display_name : "Psoxy Connector - ${local.environment_id_display_name_qualifier}${module.worklytics_connector_specs.enabled_google_workspace_connectors[id].display_name}"
85+
description : "Google API OAuth Client for ${module.worklytics_connector_specs.enabled_google_workspace_connectors[id].display_name}"
86+
expected_service_account_email : connection.service_account_email
87+
})
88+
}
89+
5590
key_creation_todos = {
5691
for id, connection in module.google_workspace_connection :
5792
id => templatefile("${path.module}/gcp-sa-key-create-todo.tftpl", { gcp_project_id : var.gcp_project_id, gcp_service_account : connection.service_account_email, secret_prefix : connection.instance_id })
5893
}
5994

60-
todos = [for id, connection in module.google_workspace_connection :
61-
local.provision_gcp_sa_keys ? connection.todo : "${local.key_creation_todos[id]}\n${connection.todo}"
62-
]
95+
connector_todos = {
96+
for id, connection in module.google_workspace_connection :
97+
id => join("\n\n", [for part in [
98+
local.enable_apis ? null : local.api_enable_todos[id],
99+
local.provision_service_accounts ? null : local.sa_creation_todos[id],
100+
connection.todo,
101+
local.provision_gcp_sa_keys ? null : local.key_creation_todos[id],
102+
] : part if part != null])
103+
}
104+
105+
todos = [for id, connection in module.google_workspace_connection : local.connector_todos[id]]
63106

64-
current_todo_step = try(max(values(module.google_workspace_connection)[*].next_todo_step...), var.todo_step)
107+
current_todo_step = try(max(values(module.google_workspace_connection)[*].next_todo_step...), local.dwd_todo_step)
65108
next_todo_step = local.provision_gcp_sa_keys ? local.current_todo_step : local.current_todo_step + 1
66109

110+
connectors_needing_manual_api_enablement = local.enable_apis ? {} : {
111+
for k, v in module.worklytics_connector_specs.enabled_google_workspace_connectors :
112+
k => v
113+
}
114+
115+
connectors_needing_manual_sa_creation = local.provision_service_accounts ? {} : {
116+
for k, v in module.worklytics_connector_specs.enabled_google_workspace_connectors :
117+
k => v
118+
}
119+
67120
service_accounts_tf_managed_keys = local.provision_gcp_sa_keys ? {
68121
for k, v in module.worklytics_connector_specs.enabled_google_workspace_connectors :
69122
k => module.google_workspace_connection[k].service_account_id
@@ -75,10 +128,24 @@ locals {
75128
}
76129
}
77130

131+
resource "local_file" "todo_gcp_api_enablement" {
132+
for_each = var.todos_as_local_files ? local.connectors_needing_manual_api_enablement : {}
133+
134+
filename = "TODO ${local.api_todo_step} - Enable APIs for ${each.key}.md"
135+
content = local.api_enable_todos[each.key]
136+
}
137+
138+
resource "local_file" "todo_gcp_sa_creation" {
139+
for_each = var.todos_as_local_files ? local.connectors_needing_manual_sa_creation : {}
140+
141+
filename = "TODO ${local.sa_todo_step} - Create Service Account for ${each.key}.md"
142+
content = local.sa_creation_todos[each.key]
143+
}
144+
78145
resource "local_file" "todo_gcp_sa_key_creation" {
79146
for_each = var.todos_as_local_files ? local.service_accounts_user_managed_keys : {}
80147

81-
filename = "TODO ${local.current_todo_step} - Create Key for ${each.key}.md"
148+
filename = "TODO ${local.key_todo_step} - Create Key for ${each.key}.md"
82149
content = local.key_creation_todos[each.key]
83150
}
84151

0 commit comments

Comments
 (0)