From 2fac479978912eaf0524ba64366f177bfbe81d69 Mon Sep 17 00:00:00 2001 From: Gus Class Date: Mon, 15 May 2017 11:16:36 -0700 Subject: [PATCH 1/3] Initial commit. --- iot/api-client/README.md | 37 ++ iot/api-client/generate_keys.sh | 20 ++ iot/api-client/manager/README.md | 43 +++ .../cloudiot_device_manager_example.py | 330 ++++++++++++++++++ iot/api-client/manager/requirements.txt | 1 + iot/api-client/mqtt_example/README.md | 42 +++ .../mqtt_example/cloudiot_mqtt_example.py | 211 +++++++++++ iot/api-client/mqtt_example/requirements.txt | 3 + iot/api-client/scripts/README.rst | 115 ++++++ iot/api-client/scripts/README.rst.in | 22 ++ iot/api-client/scripts/iam.py | 58 +++ iot/api-client/scripts/requirements.txt | 1 + 12 files changed, 883 insertions(+) create mode 100644 iot/api-client/README.md create mode 100755 iot/api-client/generate_keys.sh create mode 100644 iot/api-client/manager/README.md create mode 100644 iot/api-client/manager/cloudiot_device_manager_example.py create mode 100644 iot/api-client/manager/requirements.txt create mode 100644 iot/api-client/mqtt_example/README.md create mode 100644 iot/api-client/mqtt_example/cloudiot_mqtt_example.py create mode 100644 iot/api-client/mqtt_example/requirements.txt create mode 100644 iot/api-client/scripts/README.rst create mode 100644 iot/api-client/scripts/README.rst.in create mode 100644 iot/api-client/scripts/iam.py create mode 100644 iot/api-client/scripts/requirements.txt diff --git a/iot/api-client/README.md b/iot/api-client/README.md new file mode 100644 index 000000000000..4af475a99808 --- /dev/null +++ b/iot/api-client/README.md @@ -0,0 +1,37 @@ +# Cloud IoT Core Python Samples +This folder contains Python samples that demonstrate an overview of the +Google Cloud IoT Core platform. + +## Quickstart +1. Install the gCloud CLI as described in [the device manager guide](https://cloud.google.com/iot/docs/device_manager_guide). +2. Create a PubSub topic: + + gcloud beta pubsub topics create projects/my-iot-project/topics/device-events + +3. Add the service account `cloud-iot@system.gserviceaccount.com` to that +PubSub topic from the [Cloud Developer Console](https://console.cloud.google.com) +or by using the helper script in the /scripts folder. + +4. Create a registry: + + gcloud alpha iot registries create my-registry \ + --project=my-iot-project \ + --region=us-central1 \ + --pubsub-topic=projects/my-iot-project/topics/device-events + +5. Use the `generate_keys.sh` script to generate your signing keys: + + ./generate_keys.sh + +6. Register a device: + + gcloud alpha iot devices create my-python-device \ + --project=my-iot-project \ + --region=us-central1 \ + --registry=my-registry \ + --public-key path=rsa_cert.pem,type=rs256 + +7. Connect a sample device using the sample app in the `mqtt_example` folder. +8. Learn how to manage devices programatically with the sample app in the +`manager` folder. + diff --git a/iot/api-client/generate_keys.sh b/iot/api-client/generate_keys.sh new file mode 100755 index 000000000000..a112648e4033 --- /dev/null +++ b/iot/api-client/generate_keys.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +openssl req -x509 -newkey rsa:2048 -keyout rsa_private.pem -nodes -out \ + rsa_cert.pem -subj "/CN=unused" +openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem +openssl ec -in ec_private.pem -pubout -out ec_public.pem diff --git a/iot/api-client/manager/README.md b/iot/api-client/manager/README.md new file mode 100644 index 000000000000..94a9c235840f --- /dev/null +++ b/iot/api-client/manager/README.md @@ -0,0 +1,43 @@ +# Cloud IoT Core Device Manager Python Sample + +This sample application shows you how to manage devices programmatically using +Python. + + +# Setup + +1. Use virtualenv to create a local Python environment. + + virtualenv env && source env/bin/activate + +2. Install the dependencies + + pip install -r requirements.txt + + +# Running the sample + +The following snippet summarizes usage for the device manager sample: + + usage: cloudiot_device_manager_example.py [-h] \ + --project_id PROJECT_ID \ + --pubsub_topic PUBSUB_TOPIC \ + --api_key API_KEY \ + [--ec_public_key_file EC_PUBLIC_KEY_FILE] \ + [--rsa_certificate_file RSA_CERTIFICATE_FILE] \ + [--cloud_region CLOUD_REGION] \ + [--service_account_json SERVICE_ACCOUNT_JSON] \ + [--registry_id REGISTRY_ID] + + +For example, if your project-id is `blue-jet-123` and your service account +credentials are stored in `creds.json` in your home folder, the following +command would run the sample: + + python cloudiot_device_manager_example.py \ + --project_id blue-jet-123 \ + --pubsub_topic projects/blue-jet-123/topics/device-events \ + --ec_public_key ../ec_public.pem \ + --rsa_certificate_file ../rsa_cert.pem \ + --api_key YOUR_API_KEY \ + --service_account_json $HOME/creds.json diff --git a/iot/api-client/manager/cloudiot_device_manager_example.py b/iot/api-client/manager/cloudiot_device_manager_example.py new file mode 100644 index 000000000000..5b8b7971684c --- /dev/null +++ b/iot/api-client/manager/cloudiot_device_manager_example.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python + +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Example of using the Google Cloud IoT Core device manager to administer +devices. + +This example uses the Device Manager API to create, retrieve, disable, list and +delete Cloud IoT Core devices and registries, using both RSA and eliptic curve +keys for authentication. + +Before you run the sample, configure Cloud IoT Core as described in the +documentation at https://cloud.google.com/iot or by following the instructions +in the README located in the parent folder. + +Usage example: + + $ python cloudiot_device_manager_example.py \ + --project_id=my-project-id \ + --pubsub_topic=projects/my-project-id/topics/my-topic-id \ + --api_key=YOUR_API_KEY \ + --ec_public_key_file=ec_public.pem \ + --rsa_certificate_file=rsa_cert.pem \ + --service_account_json=service_account.json + +Troubleshooting: + + - If you get a 400 error when running the example, with the message "The API + Key and the authentication credential are from different projects" it means + that you are using the wrong API Key. Ensure that you are using the API key + from Google Cloud Platform's API Manager's Credentials page. +""" + +import argparse +import sys +import time + +from googleapiclient import discovery +from googleapiclient.errors import HttpError +from oauth2client.service_account import ServiceAccountCredentials + +API_SCOPES = ['https://www.googleapis.com/auth/cloud-platform'] +API_VERSION = 'v1alpha1' +DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest' +SERVICE_NAME = 'cloudiot' + + +def discovery_url(api_key): + """Construct the discovery url for the given api key.""" + return '{}?version={}&key={}'.format(DISCOVERY_API, API_VERSION, api_key) + + +class DeviceRegistry(object): + """Administer a set of devices for a device registry.""" + + def __init__( + self, project_id, registry_id, cloud_region, + service_account_json, api_key, pubsub_topic): + """Lookup or create a device registry for the given project.""" + self.parent = 'projects/{}/locations/{}'.format( + project_id, cloud_region) + self.full_name = '{}/registries/{}'.format(self.parent, registry_id) + credentials = ServiceAccountCredentials.from_json_keyfile_name( + service_account_json, API_SCOPES) + + if not credentials: + sys.exit( + 'Could not load service account credential from {}' + .format(service_account_json)) + + self._service = discovery.build( + SERVICE_NAME, + API_VERSION, + discoveryServiceUrl=discovery_url(api_key), + credentials=credentials) + + # Lookup or create the device registry. Here we bind the registry to + # the given Cloud Pub/Sub topic. All devices within a registry will + # have their telemetry data published to this topic, using attributes + # to indicate which device the data originated from. + registry_info = { + 'eventNotificationConfig': { + 'pubsubTopicName': pubsub_topic + } + } + request = self._service.projects().locations().registries().create( + parent=self.parent, body=registry_info, id=registry_id) + + try: + response = request.execute() + print('Created registry', registry_id) + print(response) + except HttpError as e: + if e.resp.status == 409: + # Device registry already exists + print( + 'Registry', registry_id, + 'already exists - looking it up instead.') + request = self._service.projects().locations().registries( + ).get(name=self.full_name) + request.execute() + + else: + raise + + def delete(self): + """Delete this registry.""" + request = self._service.projects().locations().registries().delete( + name=self.full_name) + return request.execute() + + def list_devices(self): + """List all devices in the registry.""" + request = self._service.projects().locations().registries().devices( + ).list(parent=self.full_name) + response = request.execute() + return response.get('devices', []) + + def _create_device(self, device_id, device_template): + request = self._service.projects().locations().registries().devices( + ).create(parent=self.full_name, body=device_template, id=device_id) + return request.execute() + + def create_device_with_rs256(self, device_id, certificate_file): + """Create a new device with the given id, using RS256 for + authentication.""" + with open(certificate_file) as f: + certificate = f.read() + + # Create a device with the given certificate. Note that you can have + # multiple credentials associated with a device. + device_template = { + 'credentials': [{ + 'publicKey': { + 'format': 'RSA_X509_PEM', + 'key': certificate + } + }] + } + return self._create_device(device_id, device_template) + + def create_device_with_es256(self, device_id, public_key_file): + """Create a new device with the given id, using ES256 for + authentication.""" + with open(public_key_file) as f: + public_key = f.read() + + # Create a device with the given public key. Note that you can have + # multiple credentials associated with a device. + device_template = { + 'credentials': [{ + 'publicKey': { + 'format': 'ES256_PEM', + 'key': public_key + } + }] + } + return self._create_device(device_id, device_template) + + def create_device_with_no_auth(self, device_id): + """Create a new device with no authentication.""" + return self._create_device(device_id, {}) + + def patch_es256_for_auth(self, device_id, public_key_file): + """Patch the device to add an ES256 public key to the device.""" + with open(public_key_file) as f: + public_key = f.read() + + patch = { + 'credentials': [{ + 'publicKey': { + 'format': 'ES256_PEM', + 'key': public_key + } + }] + } + + device_name = '{}/devices/{}'.format(self.full_name, device_id) + + # Patch requests use a FieldMask to determine which fields to update. + # In this case, we're updating the device's credentials with a new + # entry. + request = self._service.projects().locations().registries().devices( + ).patch(name=device_name, updateMask='credentials', body=patch) + + return request.execute() + + def delete_device(self, device_id): + """Delete the device with the given id.""" + device_name = '{}/devices/{}'.format(self.full_name, device_id) + request = self._service.projects().locations().registries().devices( + ).delete(name=device_name) + return request.execute() + + +def parse_command_line_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description='Example of Google Cloud IoT Core device management.') + # Required arguments + parser.add_argument( + '--project_id', required=True, help='GCP cloud project name.') + parser.add_argument( + '--pubsub_topic', + required=True, + help=('Google Cloud Pub/Sub topic. ' + 'Format is projects/project_id/topics/topic-id')) + parser.add_argument('--api_key', required=True, help='Your API key.') + + # Optional arguments + parser.add_argument( + '--ec_public_key_file', + default='ec_public.pem', + help='Path to public ES256 key file.') + parser.add_argument( + '--rsa_certificate_file', + default='rsa_cert.pem', + help='Path to RS256 certificate file.') + parser.add_argument( + '--cloud_region', default='us-central1', help='GCP cloud region') + parser.add_argument( + '--service_account_json', + default='service_account.json', + help='Path to service account json file.') + parser.add_argument( + '--registry_id', + default=None, + help='Registry id. If not set, a name will be generated.') + + return parser.parse_args() + + +def main(): + args = parse_command_line_args() + + # The example id for our registry. + if args.registry_id is None: + registry_id = 'cloudiot_device_manager_example_registry_{}'.format( + int(time.time())) + else: + registry_id = args.registry_id + + # Lookup or create the registry. + print 'Creating registry', registry_id, 'in project', args.project_id + device_registry = DeviceRegistry( + args.project_id, registry_id, args.cloud_region, + args.service_account_json, args.api_key, args.pubsub_topic) + + # List devices for the (empty) registry + print('Current devices in the registry:') + for device in device_registry.list_devices(): + print device + + # Create an RS256 authenticated device. Note that for security, it is very + # important that you use unique public/private key pairs for each device + # (do not reuse a key pair for multiple devices). This way if a private key + # is compromised, only a single device will be affected. + rs256_device_id = 'rs256-device' + print('Creating RS256 authenticated device', rs256_device_id) + device_registry.create_device_with_rs256( + rs256_device_id, args.rsa_certificate_file) + + # Create an ES256 authenticated device. To demonstrate updating a device, + # we will create the device with no authentication, and then update it to + # use ES256 for authentication. Note that while one can create a device + # without authentication, the MQTT client will not be able to connect to + # it. + es256_device_id = 'es256-device' + print('Creating device without authentication', es256_device_id) + device_registry.create_device_with_no_auth(es256_device_id) + + # Now list devices again + print('Current devices in the registry:') + for device in device_registry.list_devices(): + print(device) + + # Patch the device with authentication + print('Updating device', es256_device_id, 'to use ES256 authentication.') + device_registry.patch_es256_for_auth( + es256_device_id, args.ec_public_key_file) + + # Now list devices again + print('Current devices in the registry:') + for device in device_registry.list_devices(): + print(device) + + # Delete the ES256 device + print('Deleting device', es256_device_id) + device_registry.delete_device(es256_device_id) + + # List devices - will only show the RS256 device. + print('Current devices in the registry:') + for device in device_registry.list_devices(): + print(device) + + # Try to delete the registry. This will fail however, since the registry is + # not empty. + print('Trying to delete non-empty registry') + try: + device_registry.delete() + except HttpError as e: + # This will say that the registry is not empty. + print(e) + + # Delete the RSA devices from the registry + print('Deleting device', rs256_device_id) + device_registry.delete_device(rs256_device_id) + + # Now actually delete registry + print('Deleting registry') + device_registry.delete() + + print 'Completed successfully. Goodbye!' + + +if __name__ == '__main__': + main() diff --git a/iot/api-client/manager/requirements.txt b/iot/api-client/manager/requirements.txt new file mode 100644 index 000000000000..d8055e00cd9a --- /dev/null +++ b/iot/api-client/manager/requirements.txt @@ -0,0 +1 @@ +google-api-python-client diff --git a/iot/api-client/mqtt_example/README.md b/iot/api-client/mqtt_example/README.md new file mode 100644 index 000000000000..d36ce358a5b1 --- /dev/null +++ b/iot/api-client/mqtt_example/README.md @@ -0,0 +1,42 @@ +# Cloud IoT Core Python MQTT example + +This sample app publishes data to Cloud Pub/Sub using the MQTT bridge provided +as part of Google Cloud IoT Core. + +For detailed running instructions see the [MQTT code samples guide](https://cloud.google.com/iot/docs/protocol_bridge_guide). + +# Setup + +1. Use virtualenv to create a local Python environment. + + virtualenv env && source env/bin/activate + +2. Install the dependencies + + pip install -r requirements.txt + +# Running the Sample + +The following snippet summarizes usage: + + cloudiot_mqtt_example.py [-h] + --project_id PROJECT_ID + --registry_id REGISTRY_ID + --device_id DEVICE_ID + --private_key_file PRIVATE_KEY_FILE + --algorithm {RS256,ES256} + [--cloud_region CLOUD_REGION] + [--ca_certs CA_CERTS] + [--num_messages NUM_MESSAGES] + [--mqtt_bridge_hostname MQTT_BRIDGE_HOSTNAME] + [--mqtt_bridge_port MQTT_BRIDGE_PORT] + +For example, if your project ID is `blue-jet-123`, the following example shows +how you would execute using the configuration from the MQTT code samples guide: + + python cloudiot_mqtt_example.py \ + --registry_id my-registry \ + --project_id=blue-jet-123 \ + --device_id my-python-device \ + --algorithm RS256 \ + --private_key_file ../rsa_private.pem diff --git a/iot/api-client/mqtt_example/cloudiot_mqtt_example.py b/iot/api-client/mqtt_example/cloudiot_mqtt_example.py new file mode 100644 index 000000000000..b15ab4792c83 --- /dev/null +++ b/iot/api-client/mqtt_example/cloudiot_mqtt_example.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python + +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python sample for connecting to Google Cloud IoT Core via MQTT, using JWT. + +This example connects to Google Cloud IoT Core via MQTT, using a JWT for device +authentication. After connecting, by default the device publishes 100 messages +to the device's MQTT topic at a rate of one per second, and then exits. + +Before you run the sample, you must register your device as described in the +README in the parent folder. + +After registering the device, download Google's CA root certificates with + + wget https://pki.google.com/roots.pem + +and run this script with the corresponding algorithm flag, for example: + + python cloudiot_mqtt_example.py --project_id=my-project-id \ + --registry_id=my-registry-id \ + --device_id=my-device-id \ + --private_key_file=rsa_private.pem \ + --algorithm=RS256 +""" + +import argparse +import datetime +import time + +import jwt +import paho.mqtt.client as mqtt + + +def create_jwt(project_id, private_key_file, algorithm): + """Creates a JWT (https://jwt.io) to establish an MQTT connection. + + Args: + project_id: The cloud project ID this device belongs to + + private_key_file: A path to a file containing either an RSA256 or ES256 + private key. + + algorithm: The encryption algorithm to use. Either 'RS256' or 'ES256' + + Returns: + An MQTT generated from the given project_id and private key, which + expires in 20 minutes. After 20 minutes, your client will be + disconnected, and a new JWT will have to be generated. + + Raises: + ValueError: If the private_key_file does not contain a known key. + """ + + token = { + # The time that the token was issued at + 'iat': datetime.datetime.utcnow(), + # When this token expires. The device will be disconnected after the + # token expires, and will have to reconnect. + 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), + # The audience field should always be set to the GCP project id. + 'aud': project_id + } + + # Read the private key file. + with open(private_key_file, 'r') as f: + private_key = f.read() + + print( + 'Creating JWT using {} from private key file {}'.format( + algorithm, private_key_file)) + + return jwt.encode(token, private_key, algorithm=algorithm) + + +def error_str(rc): + """Convert a Paho error to a human readable string.""" + return '{}: {}'.format(rc, mqtt.error_string(rc)) + + +def on_connect(unused_client, unused_userdata, unused_flags, rc): + """Callback for when a device connects.""" + print 'on_connect', error_str(rc) + + +def on_disconnect(unused_client, unused_userdata, rc): + """Paho callback for when a device disconnects.""" + print 'on_disconnect', error_str(rc) + + +def on_publish(unused_client, unused_userdata, unused_mid): + """Paho callback when a message is sent to the broker.""" + print 'on_publish' + + +def parse_command_line_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description=( + 'Example Google Cloud IoT Core MQTT device connection code.')) + parser.add_argument( + '--project_id', + required=True, + help='GCP cloud project name') + parser.add_argument( + '--registry_id', + required=True, + help='Cloud IoT Core registry id') + parser.add_argument( + '--device_id', + required=True, + help='Cloud IoT Core device id') + parser.add_argument( + '--private_key_file', + required=True, + help='Path to private key file.') + parser.add_argument( + '--algorithm', + choices=('RS256', 'ES256'), + required=True, + help='Which encryption algorithm to use to generate the JWT.') + parser.add_argument( + '--cloud_region', default='us-central1', help='GCP cloud region') + parser.add_argument( + '--ca_certs', + default='roots.pem', + help=('CA root certificate from https://pki.google.com/roots.pem')) + parser.add_argument( + '--num_messages', + type=int, + default=100, + help='Number of messages to publish.') + parser.add_argument( + '--mqtt_bridge_hostname', + default='mqtt.googleapis.com', + help='MQTT bridge hostname.') + parser.add_argument( + '--mqtt_bridge_port', default=8883, help='MQTT bridge port.') + + return parser.parse_args() + + +def main(): + args = parse_command_line_args() + + # Create our MQTT client. The client_id is a unique string that identifies + # this device. For Google Cloud IoT Core, it must be in the format below. + client = mqtt.Client( + client_id=( + 'projects/{}/locations/{}/registries/{}/devices/{}' + .format( + args.project_id, args.cloud_region, + args.registry_id, args.device_id))) + + # With Google Cloud IoT Core, the username field is ignored, and the + # password field is used to transmit a JWT to authorize the device. + client.username_pw_set( + username='unused', + password=create_jwt( + args.project_id, args.private_key_file, args.algorithm)) + + # Enable SSL/TLS support. + client.tls_set(ca_certs=args.ca_certs) + + # Register message callbacks. https://eclipse.org/paho/clients/python/docs/ + # describes additional callbacks that Paho supports. In this example, the + # callbacks just print to standard out. + client.on_connect = on_connect + client.on_publish = on_publish + client.on_disconnect = on_disconnect + + # Connect to the Google MQTT bridge. + client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port) + + # Start the network loop. + client.loop_start() + + mqtt_topic = '/devices/{}/events'.format(args.device_id) + + # Publish num_messages mesages to the MQTT bridge once per second. + for i in range(1, args.num_messages + 1): + payload = '{}/{}-payload-{}'.format( + args.registry_id, args.device_id, i) + print( + 'Publishing message {}/{}: \'{}\''.format( + i, args.num_messages, payload)) + # Publish "payload" to the MQTT topic. qos=1 means at least once + # delivery. Cloud IoT Core also supports qos=0 for at most once + # delivery. + client.publish(mqtt_topic, payload, qos=1) + time.sleep(1) + + # End the network loop and finish. + client.loop_stop() + print('Finished.') + + +if __name__ == '__main__': + main() diff --git a/iot/api-client/mqtt_example/requirements.txt b/iot/api-client/mqtt_example/requirements.txt new file mode 100644 index 000000000000..5608b4fd4192 --- /dev/null +++ b/iot/api-client/mqtt_example/requirements.txt @@ -0,0 +1,3 @@ +cryptography +pyjwt +paho-mqtt diff --git a/iot/api-client/scripts/README.rst b/iot/api-client/scripts/README.rst new file mode 100644 index 000000000000..2d48c4a9abac --- /dev/null +++ b/iot/api-client/scripts/README.rst @@ -0,0 +1,115 @@ +.. This file is automatically generated. Do not edit this file directly. + +Google Cloud IoT Core Python Samples +=============================================================================== + +This directory contains samples for Google Cloud IoT Core. `Google Cloud IoT Core`_ is a fully-managed, globally distributed solution for managing devices and sending / receiving messages from devices. Set the `GOOGLE_CLOUD_PROJECT` environment variable and call the script with your topic ID. + + + + +.. _Google Cloud IoT Core: https://cloud.google.com/iot/ + +Setup +------------------------------------------------------------------------------- + + +Authentication +++++++++++++++ + +Authentication is typically done through `Application Default Credentials`_, +which means you do not have to change the code to authenticate as long as +your environment has credentials. You have a few options for setting up +authentication: + +#. When running locally, use the `Google Cloud SDK`_ + + .. code-block:: bash + + gcloud auth application-default login + + +#. When running on App Engine or Compute Engine, credentials are already + set-up. However, you may need to configure your Compute Engine instance + with `additional scopes`_. + +#. You can create a `Service Account key file`_. This file can be used to + authenticate to Google Cloud Platform services from any environment. To use + the file, set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to + the path to the key file, for example: + + .. code-block:: bash + + export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account.json + +.. _Application Default Credentials: https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow +.. _additional scopes: https://cloud.google.com/compute/docs/authentication#using +.. _Service Account key file: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount + +Install Dependencies +++++++++++++++++++++ + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- + +PubSub helper ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + +To run this sample: + +.. code-block:: bash + + $ python iam.py + + usage: iam.py [-h] topic_name + + This application demonstrates how programatically grant access to the + Cloud IoT Core service account on a given PubSub topic. + + For more information, see https://cloud.google.com/iot. + + positional arguments: + topic_name The PubSub topic to grant Cloud IoT Core access to + + optional arguments: + -h, --help show this help message and exit + + + + +The client library +------------------------------------------------------------------------------- + +This sample uses the `Google Cloud Client Library for Python`_. +You can read the documentation for more details on API usage and use GitHub +to `browse the source`_ and `report issues`_. + +.. Google Cloud Client Library for Python: + https://googlecloudplatform.github.io/google-cloud-python/ +.. browse the source: + https://github.com/GoogleCloudPlatform/google-cloud-python +.. report issues: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/iot/api-client/scripts/README.rst.in b/iot/api-client/scripts/README.rst.in new file mode 100644 index 000000000000..dfd077ff41b5 --- /dev/null +++ b/iot/api-client/scripts/README.rst.in @@ -0,0 +1,22 @@ +# This file is used to generate README.rst + +product: + name: Google Cloud IoT Core + short_name: Cloud IoT Core + url: https://cloud.google.com/iot/ + description: > + `Google Cloud IoT Core`_ is a fully-managed, globally distributed solution + for managing devices and sending / receiving messages from devices. Set the + `GOOGLE_CLOUD_PROJECT` environment variable and call the script with your + topic ID. + +setup: +- auth +- install_deps + +samples: +- name: PubSub helper + file: iam.py + show_help: true + +cloud_client_library: true diff --git a/iot/api-client/scripts/iam.py b/iot/api-client/scripts/iam.py new file mode 100644 index 000000000000..0ce69c8f9109 --- /dev/null +++ b/iot/api-client/scripts/iam.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This application demonstrates how programatically grant access to the +Cloud IoT Core service account on a given PubSub topic. + +For more information, see https://cloud.google.com/iot. +""" + +import argparse + +from google.cloud import pubsub + + +def set_topic_policy(topic_name): + """Sets the IAM policy for a topic for Cloud IoT Core.""" + pubsub_client = pubsub.Client() + topic = pubsub_client.topic(topic_name) + policy = topic.get_iam_policy() + + # Add a group as publishers. + publishers = policy.get('roles/pubsub.publisher', []) + publishers.append(policy.service_account( + 'cloud-iot@system.gserviceaccount.com')) + policy['roles/pubsub.publisher'] = publishers + + # Set the policy + topic.set_iam_policy(policy) + + print('IAM policy for topic {} set.'.format(topic.name)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + subparsers = parser.add_argument( + dest='topic_name', + help='The PubSub topic to grant Cloud IoT Core access to') + + args = parser.parse_args() + + set_topic_policy(args.topic_name) diff --git a/iot/api-client/scripts/requirements.txt b/iot/api-client/scripts/requirements.txt new file mode 100644 index 000000000000..1412eed3dcae --- /dev/null +++ b/iot/api-client/scripts/requirements.txt @@ -0,0 +1 @@ +google-cloud-pubsub==0.25.0 From d7c3b6828597fc09de64ad2e8062660f459b90e1 Mon Sep 17 00:00:00 2001 From: Gus Class Date: Thu, 25 May 2017 09:39:07 -0700 Subject: [PATCH 2/3] Changes oauth2 client --- .../cloudiot_device_manager_example.py | 23 +++++++++++-------- iot/api-client/manager/requirements.txt | 3 ++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/iot/api-client/manager/cloudiot_device_manager_example.py b/iot/api-client/manager/cloudiot_device_manager_example.py index 5b8b7971684c..6bf38c6c85b9 100644 --- a/iot/api-client/manager/cloudiot_device_manager_example.py +++ b/iot/api-client/manager/cloudiot_device_manager_example.py @@ -48,12 +48,12 @@ import sys import time +from google.oauth2 import service_account from googleapiclient import discovery from googleapiclient.errors import HttpError -from oauth2client.service_account import ServiceAccountCredentials API_SCOPES = ['https://www.googleapis.com/auth/cloud-platform'] -API_VERSION = 'v1alpha1' +API_VERSION = 'v1beta1' DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest' SERVICE_NAME = 'cloudiot' @@ -73,8 +73,9 @@ def __init__( self.parent = 'projects/{}/locations/{}'.format( project_id, cloud_region) self.full_name = '{}/registries/{}'.format(self.parent, registry_id) - credentials = ServiceAccountCredentials.from_json_keyfile_name( - service_account_json, API_SCOPES) + credentials = service_account.Credentials.from_service_account_file( + service_account_json) + scoped_credentials = credentials.with_scopes(API_SCOPES) if not credentials: sys.exit( @@ -85,19 +86,20 @@ def __init__( SERVICE_NAME, API_VERSION, discoveryServiceUrl=discovery_url(api_key), - credentials=credentials) + credentials=scoped_credentials) # Lookup or create the device registry. Here we bind the registry to # the given Cloud Pub/Sub topic. All devices within a registry will # have their telemetry data published to this topic, using attributes # to indicate which device the data originated from. - registry_info = { + body = { 'eventNotificationConfig': { 'pubsubTopicName': pubsub_topic - } + }, + 'id': registry_id } request = self._service.projects().locations().registries().create( - parent=self.parent, body=registry_info, id=registry_id) + parent=self.parent, body=body) try: response = request.execute() @@ -129,7 +131,7 @@ def list_devices(self): response = request.execute() return response.get('devices', []) - def _create_device(self, device_id, device_template): + def _create_device(self, device_template): request = self._service.projects().locations().registries().devices( ).create(parent=self.full_name, body=device_template, id=device_id) return request.execute() @@ -143,6 +145,7 @@ def create_device_with_rs256(self, device_id, certificate_file): # Create a device with the given certificate. Note that you can have # multiple credentials associated with a device. device_template = { + 'id': device_id, 'credentials': [{ 'publicKey': { 'format': 'RSA_X509_PEM', @@ -150,7 +153,7 @@ def create_device_with_rs256(self, device_id, certificate_file): } }] } - return self._create_device(device_id, device_template) + return self._create_device(device_template) def create_device_with_es256(self, device_id, public_key_file): """Create a new device with the given id, using ES256 for diff --git a/iot/api-client/manager/requirements.txt b/iot/api-client/manager/requirements.txt index d8055e00cd9a..71268a17e9a4 100644 --- a/iot/api-client/manager/requirements.txt +++ b/iot/api-client/manager/requirements.txt @@ -1 +1,2 @@ -google-api-python-client +google-api-python-client==1.6.2 +google-auth==1.0.1 From efe8f8eaa05c641f9b43e4666d4b08204c13d930 Mon Sep 17 00:00:00 2001 From: Gus Class Date: Thu, 25 May 2017 10:25:34 -0700 Subject: [PATCH 3/3] Fixes google.auth and addresses changes in v1beta1 --- .../manager/cloudiot_device_manager_example.py | 10 +++++++--- iot/api-client/manager/requirements.txt | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/iot/api-client/manager/cloudiot_device_manager_example.py b/iot/api-client/manager/cloudiot_device_manager_example.py index 6bf38c6c85b9..ce7130dc1c51 100644 --- a/iot/api-client/manager/cloudiot_device_manager_example.py +++ b/iot/api-client/manager/cloudiot_device_manager_example.py @@ -133,7 +133,7 @@ def list_devices(self): def _create_device(self, device_template): request = self._service.projects().locations().registries().devices( - ).create(parent=self.full_name, body=device_template, id=device_id) + ).create(parent=self.full_name, body=device_template) return request.execute() def create_device_with_rs256(self, device_id, certificate_file): @@ -164,6 +164,7 @@ def create_device_with_es256(self, device_id, public_key_file): # Create a device with the given public key. Note that you can have # multiple credentials associated with a device. device_template = { + 'id': device_id, 'credentials': [{ 'publicKey': { 'format': 'ES256_PEM', @@ -171,11 +172,14 @@ def create_device_with_es256(self, device_id, public_key_file): } }] } - return self._create_device(device_id, device_template) + return self._create_device(device_template) def create_device_with_no_auth(self, device_id): """Create a new device with no authentication.""" - return self._create_device(device_id, {}) + device_template = { + 'id': device_id, + } + return self._create_device(device_template) def patch_es256_for_auth(self, device_id, public_key_file): """Patch the device to add an ES256 public key to the device.""" diff --git a/iot/api-client/manager/requirements.txt b/iot/api-client/manager/requirements.txt index 71268a17e9a4..00e4531fdc2a 100644 --- a/iot/api-client/manager/requirements.txt +++ b/iot/api-client/manager/requirements.txt @@ -1,2 +1,3 @@ google-api-python-client==1.6.2 +google-auth-httplib2==0.0.2 google-auth==1.0.1