Skip to content

Commit 8efc802

Browse files
Create setup-iot-core-on-google-cloud-with-terraform
1 parent 1bbc828 commit 8efc802

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
---
2+
canonical_url: https://dev.to/networkandcode/setup-iot-core-on-google-cloud-with-terraform-4h6a
3+
cover_image: https://source.unsplash.com/featured/?iot
4+
tags: cloud, googlecloud, iot, terraform
5+
---
6+
7+
Hello :wave:, we shall see how to provision a minimal IoT infrastructure on Google cloud with Terraform.
8+
9+
I shall be doing this straight on the Google cloud shell...
10+
11+
## Project
12+
Set your gcloud config...
13+
14+
Get you projects list and set one of the projects as the current project.
15+
```
16+
$ gcloud projects list
17+
18+
$ gcloud config set project <project-id>
19+
```
20+
21+
## Directories
22+
Let's create two directories for the terraform resources, one for the service account and another for rest of the resources.
23+
```
24+
$ mkdir ~/sa
25+
$ mkdir ~/iot
26+
```
27+
and one more hidden directory for storing the keys/certificates.
28+
```
29+
$ mkdir ~/.auth
30+
```
31+
32+
## TF Provider
33+
We would set the Terraform provider configuration here.
34+
35+
Get the list of zones in the specific region. Note that cloud IoT is currently supported in these regions: asia-east1, europe-west1, us-central1.
36+
```
37+
$ gcloud compute zones list --filter="region~asia-east1" | grep -i name
38+
NAME: asia-east1-b
39+
NAME: asia-east1-a
40+
NAME: asia-east1-c
41+
```
42+
43+
I would be using zone c.
44+
45+
Set the provider details in terraform with the available information.
46+
```
47+
$ cat ~/sa/main.tf
48+
provider "google" {
49+
project = "<project-id>"
50+
region = "asia-east1"
51+
zone = "asia-east1-c"
52+
}
53+
54+
$ cp ~/sa/main.tf ~/iot/main.tf
55+
```
56+
57+
## Service account
58+
We are going to create a service account from our user account, which could be further used for creating other resources using terraform.
59+
```
60+
$ ls ~/sa
61+
main.tf outputs.tf sa.tf
62+
63+
$ cat ~/sa/sa.tf
64+
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account
65+
66+
resource "google_service_account" "iot_sa" {
67+
account_id = "iot-sa"
68+
display_name = "IoT Service Account"
69+
}
70+
71+
# note this requires the terraform to be run regularly
72+
resource "time_rotating" "iot_sa_key_rotation" {
73+
rotation_days = 30
74+
}
75+
76+
resource "google_service_account_key" "iot_sa_key" {
77+
service_account_id = google_service_account.iot_sa.name
78+
79+
keepers = {
80+
rotation_time = time_rotating.iot_sa_key_rotation.rotation_rfc3339
81+
}
82+
}
83+
84+
resource "google_project_iam_member" "iot_editor" {
85+
project = var.project_id
86+
role = "roles/cloudiot.editor"
87+
member = "serviceAccount:${google_service_account.iot_sa.email}"
88+
89+
condition {
90+
title = "expires_after_2022_07_31"
91+
description = "Expiring at midnight of 2022-07-31"
92+
expression = "request.time < timestamp(\"2022-08-01T00:00:00Z\")"
93+
}
94+
}
95+
96+
resource "google_project_iam_member" "pub_sub_editor" {
97+
project = var.project_id
98+
role = "roles/pubsub.editor"
99+
member = "serviceAccount:${google_service_account.iot_sa.email}"
100+
101+
condition {
102+
title = "expires_after_2022_07_31"
103+
description = "Expiring at midnight of 2022-07-31"
104+
expression = "request.time < timestamp(\"2022-08-01T00:00:00Z\")"
105+
}
106+
}
107+
108+
$ cat ~/sa/variables.tf
109+
variable "project_id" {
110+
type = string
111+
default = "<project-id>"
112+
}
113+
114+
$ cat ~/sa/outputs.tf
115+
output "iot_sa_private_key" {
116+
description = "Private key of the IoT service account"
117+
value = google_service_account_key.iot_sa_key.private_key
118+
sensitive = true
119+
}
120+
```
121+
122+
So we are creating a service account with editor roles on IoT core & Pub/Sub, a key for the service account with rotation, and then we would output the private key to save it locally for future use.
123+
124+
## API
125+
We have to enable the Cloud IoT [API](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_service), you can get the fqdn of it using `$ gcloud services list --available --filter="name~.*iot.*"`. Let's add the terraform configuration which can enable it.
126+
```
127+
$ cat ~/sa/variables.tf
128+
variable "project_id" {
129+
type = string
130+
default = "<project-id>"
131+
}
132+
133+
$ cat ~/sa/apis.tf
134+
resource "google_project_service" "cloudiot" {
135+
project = var.project_id
136+
service = "cloudiot.googleapis.com"
137+
138+
timeouts {
139+
create = "30m"
140+
update = "40m"
141+
}
142+
143+
disable_dependent_services = true
144+
}
145+
```
146+
147+
## Apply
148+
We can now create the service account, it's associated resources, and enable the Cloud IoT API.
149+
```
150+
$ cd ~/sa
151+
$ terraform init
152+
153+
# optional, to know what will be changed
154+
$ terraform plan
155+
156+
$ terraform apply --auto-approve
157+
```
158+
159+
## Validate
160+
Validate the service account creation, via the console.
161+
![Service account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osu1meit8btydayjxdx9.png)
162+
163+
And the roles attached to it.
164+
![Service account roles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yg9mlgi34dhr1jgbe229.png)
165+
166+
## Key
167+
The private key of the service account could be retrieved from the terraform output.
168+
```
169+
$ terraform output -raw iot_sa_private_key | base64 -d > ~/.auth/iot_sa_private_key.json
170+
```
171+
We have saved the base64 decoded private key in a hidden auth directory at home.
172+
173+
## Credentials
174+
We could now start using the service principal's private key as a credential for rest of our Terraform activities, for which we have to set an environment variable.
175+
```
176+
$ export GOOGLE_APPLICATION_CREDENTIALS=~/.auth/iot_sa_private_key.json
177+
```
178+
179+
Note: to remove the credential anytime, jus run `unset GOOGLE_APPLICATION_CREDENTIALS`
180+
181+
## Certificate
182+
The connection between the IoT devices and Google IoT core would be secure over TLS, hence a [certificate](https://cloud.google.com/iot/docs/create-device-registry#create_your_credentials) should be generated for our virtual device.
183+
```
184+
$ openssl req -x509 -newkey rsa:2048 -keyout ~/.auth/rsa_private.pem -nodes -out ~/.auth/rsa_cert.pem -subj "/CN=unused"
185+
186+
$ ls ~/.auth/ | grep pem
187+
rsa_cert.pem
188+
rsa_private.pem
189+
```
190+
The private key is in rsa_private.pem and the public certificate is in rsa_cert.pem.
191+
192+
We would keep the private key locally and refer to it while generating a client connection from our device(we are not dealing with the client side of things in this post though), where as the public certificate would be attached to the remote side, in this case, the IoT core.
193+
194+
## Registry
195+
Add the [terrafaorm](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_registry) configuration for the device registry, pub/sub topics it would use.
196+
```
197+
$ cd ~/iot
198+
199+
$ cat registry.tf
200+
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_registry
201+
202+
resource "google_cloudiot_registry" "iot-registry" {
203+
name = "iot-registry"
204+
205+
event_notification_configs {
206+
pubsub_topic_name = google_pubsub_topic.additional-telemetry.id
207+
subfolder_matches = "test/path"
208+
}
209+
210+
event_notification_configs {
211+
pubsub_topic_name = google_pubsub_topic.default-telemetry.id
212+
subfolder_matches = ""
213+
}
214+
215+
state_notification_config = {
216+
pubsub_topic_name = google_pubsub_topic.default-devicestatus.id
217+
}
218+
219+
mqtt_config = {
220+
mqtt_enabled_state = "MQTT_ENABLED"
221+
}
222+
223+
http_config = {
224+
http_enabled_state = "HTTP_ENABLED"
225+
}
226+
227+
log_level = "INFO"
228+
}
229+
```
230+
We would be using 3 topics, all messages published by the client to the path /devices/DEVICE_ID/events would go to the default telemetry topic, and all messages for /devices/DEVICE_ID/state would go to the default device state topic. We have one additional topic with sub folder path "test/path" which means the messages published to /devices/DEVICE_ID/events/test/path would land there.
231+
232+
## Pub/Sub
233+
A separate file for creating the pub/sub topics which will be linked to the registry.
234+
```
235+
resource "google_pubsub_topic" "default-devicestatus" {
236+
name = "default-devicestatus"
237+
}
238+
239+
resource "google_pubsub_topic" "default-telemetry" {
240+
name = "default-telemetry"
241+
}
242+
243+
resource "google_pubsub_topic" "additional-telemetry" {
244+
name = "additional-telemetry"
245+
}
246+
```
247+
## Devices
248+
We would be creating two devices, a basic one which should bind with the gateway, and an advanced device that could be standalone with out a gateway.
249+
250+
The authentication for the basic device will be handled by the gateway and hence, we don't have to set any credentials for the basic device.
251+
```
252+
$ cat basic-device.tf
253+
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_device
254+
255+
resource "google_cloudiot_device" "basic-device" {
256+
name = "basic-device"
257+
registry = google_cloudiot_registry.iot-registry.id
258+
}
259+
260+
$ cat advanced-device.tf
261+
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_device
262+
263+
resource "google_cloudiot_device" "advanced-device" {
264+
name = "advanced-device"
265+
registry = google_cloudiot_registry.iot-registry.id
266+
267+
credentials {
268+
public_key {
269+
format = "RSA_X509_PEM"
270+
key = file("~/.auth/rsa_cert.pem")
271+
}
272+
}
273+
}
274+
```
275+
276+
## Gateway
277+
And now the gateway.
278+
```
279+
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_device
280+
281+
resource "google_cloudiot_device" "iot-gateway" {
282+
name = "iot-gateway"
283+
registry = google_cloudiot_registry.iot-registry.id
284+
285+
credentials {
286+
public_key {
287+
format = "RSA_X509_PEM"
288+
key = file("~/.auth/rsa_cert.pem")
289+
}
290+
}
291+
292+
gateway_config {
293+
gateway_type = "GATEWAY"
294+
gateway_auth_method = "ASSOCIATION_ONLY"
295+
}
296+
}
297+
```
298+
I have setup ASSOCIATION_ONLY as the auth method, which means the device I will bind to this gateway would rely on this gateway for authentication and woudln't authenticate with its own credential.
299+
300+
## Apply
301+
The resources can be created.
302+
```
303+
$ terraform init
304+
305+
# optional, to see what will change
306+
$ terraform plan
307+
308+
$ terraform apply
309+
```
310+
311+
## Bind
312+
The basic device should be bounded with the gateway, so that the gateway generate JWTs on behalf of the device.
313+
```
314+
$ gcloud iot devices gateways bind --gateway iot-gateway --gateway-region asia-east1 --gateway-registry iot-registry --device basic-device --device-region asia-east1
315+
```
316+
I used gcloud for binding the device with the gateway as I was not able to quite find it in the terraform registry.
317+
318+
## Validate
319+
Finally, check the resources on the console.
320+
321+
*Registry*
322+
![Registry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy51868va1nsh7wet6b4.png)
323+
324+
*Devices*
325+
![Devices](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aikr23urwn2o5sj9h9qu.png)
326+
327+
*Gateway*
328+
![Gateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tayjuc9w5we9nwmsawrj.png)
329+
330+
*Device binding*
331+
![Device binding](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f65crdcmkjl60zzu6u3b.png)
332+
333+
Seems all good :)
334+
335+
## Graph
336+
Let's look at the graph that terraform can generate. We can view it on the cloud shell editor itself.
337+
338+
```
339+
$ terraform graph | dot -Tsvg > graph.svg
340+
341+
$ ls *.svg
342+
graph.svg
343+
```
344+
345+
![Graph](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yobgj0gj78kor9hxoymo.png)
346+
347+
With this the post is complete, thanks for reading !!!

0 commit comments

Comments
 (0)