Skip to content

Commit 9d89a89

Browse files
authored
Fail fast when IoT Hub connection string is given (#39)
1 parent df08ce4 commit 9d89a89

File tree

5 files changed

+124
-73
lines changed

5 files changed

+124
-73
lines changed

iotedgehubdev/cli.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
import json
12
import sys
3+
from functools import wraps
4+
25
import click
3-
import json
4-
from .output import Output
5-
from .hostplatform import HostPlatform
6+
7+
from . import configs, telemetry
68
from .edgecert import EdgeCert
79
from .edgemanager import EdgeManager
10+
from .hostplatform import HostPlatform
11+
from .output import Output
812
from .utils import Utils
9-
from functools import wraps
10-
from . import configs
11-
from . import telemetry
1213

1314
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=120)
1415
output = Output()
@@ -59,23 +60,28 @@ def main():
5960
help='GatewayHostName value for the module to connect.')
6061
@_with_telemetry
6162
def setup(connection_string, gateway_host):
62-
gateway_host = gateway_host.lower()
63-
fileType = 'edgehub.config'
64-
certDir = HostPlatform.get_default_cert_path()
65-
Utils.mkdir_if_needed(certDir)
66-
edgeCert = EdgeCert(certDir, gateway_host)
67-
edgeCert.generate_self_signed_certs()
68-
configFile = HostPlatform.get_config_file_path()
69-
Utils.delete_file(configFile, fileType)
70-
Utils.mkdir_if_needed(HostPlatform.get_config_path())
71-
configDict = {
72-
CONN_STR: connection_string,
73-
CERT_PATH: certDir,
74-
GATEWAY_HOST: gateway_host
75-
}
76-
configJson = json.dumps(configDict, indent=2, sort_keys=True)
77-
Utils.create_file(configFile, configJson, fileType)
78-
output.info('Setup EdgeHub runtime successfully.')
63+
try:
64+
Utils.parse_device_connection_str(connection_string)
65+
gateway_host = gateway_host.lower()
66+
fileType = 'edgehub.config'
67+
certDir = HostPlatform.get_default_cert_path()
68+
Utils.mkdir_if_needed(certDir)
69+
edgeCert = EdgeCert(certDir, gateway_host)
70+
edgeCert.generate_self_signed_certs()
71+
configFile = HostPlatform.get_config_file_path()
72+
Utils.delete_file(configFile, fileType)
73+
Utils.mkdir_if_needed(HostPlatform.get_config_path())
74+
configDict = {
75+
CONN_STR: connection_string,
76+
CERT_PATH: certDir,
77+
GATEWAY_HOST: gateway_host
78+
}
79+
configJson = json.dumps(configDict, indent=2, sort_keys=True)
80+
Utils.create_file(configFile, configJson, fileType)
81+
output.info('Setup EdgeHub runtime successfully.')
82+
except Exception as e:
83+
output.error('Error: {0}.'.format(str(e)))
84+
sys.exit(1)
7985

8086

8187
@click.command(context_settings=CONTEXT_SETTINGS,
@@ -98,16 +104,16 @@ def setup(connection_string, gateway_host):
98104
def modulecred(local, output_file):
99105
configFile = HostPlatform.get_config_file_path()
100106
if Utils.check_if_file_exists(configFile) is not True:
101-
output.error('Cannot find config file. Please setup first')
107+
output.error('Cannot find config file. Please run `iotedgehubdev setup` first.')
102108
sys.exit(1)
103109
try:
104110
with open(configFile) as f:
105111
jsonObj = json.load(f)
106112
if CONN_STR in jsonObj and CERT_PATH in jsonObj and GATEWAY_HOST in jsonObj:
107-
connectionString = jsonObj[CONN_STR]
108-
certPath = jsonObj[CERT_PATH]
113+
connection_str = jsonObj[CONN_STR]
114+
cert_path = jsonObj[CERT_PATH]
109115
gatewayhost = jsonObj[GATEWAY_HOST]
110-
edgeManager = EdgeManager(connectionString, gatewayhost, certPath)
116+
edgeManager = EdgeManager(connection_str, gatewayhost, cert_path)
111117
credential = edgeManager.outputModuleCred('target', local, output_file)
112118
output.info(credential[0])
113119
output.info(credential[1])
@@ -150,10 +156,10 @@ def start(inputs, port, deployment, verbose):
150156
with open(configFile) as f:
151157
jsonObj = json.load(f)
152158
if CONN_STR in jsonObj and CERT_PATH in jsonObj and GATEWAY_HOST in jsonObj:
153-
connectionString = jsonObj[CONN_STR]
154-
certPath = jsonObj[CERT_PATH]
159+
connection_str = jsonObj[CONN_STR]
160+
cert_path = jsonObj[CERT_PATH]
155161
gatewayhost = jsonObj[GATEWAY_HOST]
156-
edgeManager = EdgeManager(connectionString, gatewayhost, certPath)
162+
edgeManager = EdgeManager(connection_str, gatewayhost, cert_path)
157163
else:
158164
output.error('Missing keys in config file. Please run `iotedgehubdev setup` again.')
159165
sys.exit(1)

iotedgehubdev/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
class EdgeConstants():
2+
HOSTNAME_KEY = 'HostName'
3+
DEVICE_ID_KEY = 'DeviceId'
4+
ACCESS_KEY_KEY = 'SharedAccessKey'
5+
26
SUBJECT_COUNTRY_KEY = 'countryCode'
37
SUBJECT_STATE_KEY = 'state'
48
SUBJECT_LOCALITY_KEY = 'locality'

iotedgehubdev/edgemanager.py

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import requests
21
import json
3-
import docker
42
import os
3+
4+
import docker
5+
import requests
6+
7+
from .composeproject import ComposeProject
58
from .constants import EdgeConstants as EC
6-
from .utils import Utils
7-
from .errors import ResponseError
8-
from .edgedockerclient import EdgeDockerClient
99
from .edgecert import EdgeCert
10-
from .composeproject import ComposeProject
10+
from .edgedockerclient import EdgeDockerClient
11+
from .errors import ResponseError
1112
from .hostplatform import HostPlatform
13+
from .utils import Utils
1214

1315

1416
class EdgeManager(object):
15-
HOST_PREFIX = 'HostName='
16-
DEVICE_PREFIX = 'DeviceId='
17-
KEY_PREFIX = 'SharedAccessKey='
1817
LABEL = 'iotedgehubdev'
1918
EDGEHUB_IMG = 'mcr.microsoft.com/azureiotedge-hub:1.0'
2019
TESTUTILITY_IMG = 'mcr.microsoft.com/azureiotedge-testing-utility:1.0.0-rc1'
@@ -36,26 +35,16 @@ class EdgeManager(object):
3635
HELPER_IMG = 'hello-world:latest'
3736
COMPOSE_FILE = os.path.join(HostPlatform.get_config_path(), 'docker-compose.yml')
3837

39-
def __init__(self, connectionStr, gatewayhost, certPath):
40-
values = connectionStr.split(';')
41-
self.hostname = ''
42-
self.deviceId = ''
43-
self.key = ''
38+
def __init__(self, connection_str, gatewayhost, cert_path):
39+
connection_str_dict = Utils.parse_device_connection_str(connection_str)
40+
self.hostname = connection_str_dict[EC.HOSTNAME_KEY]
41+
self.device_id = connection_str_dict[EC.DEVICE_ID_KEY]
42+
self.access_key = connection_str_dict[EC.ACCESS_KEY_KEY]
4443
self.compose_file = None
45-
46-
for val in values:
47-
stripped = val.strip()
48-
if stripped.startswith(EdgeManager.HOST_PREFIX):
49-
self.hostname = stripped[len(EdgeManager.HOST_PREFIX):]
50-
elif stripped.startswith(EdgeManager.DEVICE_PREFIX):
51-
self.deviceId = stripped[len(EdgeManager.DEVICE_PREFIX):]
52-
elif stripped.startswith(EdgeManager.KEY_PREFIX):
53-
self.key = stripped[len(EdgeManager.KEY_PREFIX):]
54-
5544
self.gatewayhost = gatewayhost
56-
self.deviceUri = '{0}/devices/{1}'.format(self.hostname, self.deviceId)
57-
self.certPath = certPath
58-
self.edgeCert = EdgeCert(self.certPath, self.gatewayhost)
45+
self.device_uri = '{0}/devices/{1}'.format(self.hostname, self.device_id)
46+
self.cert_path = cert_path
47+
self.edge_cert = EdgeCert(self.cert_path, self.gatewayhost)
5948

6049
@staticmethod
6150
def stop(edgedockerclient=None):
@@ -104,7 +93,7 @@ def start_singlemodule(self, inputs, port):
10493
edgedockerclient.copy_file_to_volume(
10594
EdgeManager.INPUT, self._device_cert(),
10695
EdgeManager.MODULE_MOUNT,
107-
self.edgeCert.get_cert_file_path(EC.EDGE_DEVICE_CA))
96+
self.edge_cert.get_cert_file_path(EC.EDGE_DEVICE_CA))
10897
edgedockerclient.start(inputContainer.get('Id'))
10998

11099
def config_solution(self, deployment_config, target):
@@ -192,13 +181,13 @@ def _prepare_cert(self, edgedockerclient):
192181

193182
edgedockerclient.copy_file_to_volume(
194183
EdgeManager.CERT_HELPER, EdgeManager._chain_cert(),
195-
EdgeManager.HUB_MOUNT, self.edgeCert.get_cert_file_path(EC.EDGE_CHAIN_CA))
184+
EdgeManager.HUB_MOUNT, self.edge_cert.get_cert_file_path(EC.EDGE_CHAIN_CA))
196185
edgedockerclient.copy_file_to_volume(
197186
EdgeManager.CERT_HELPER, EdgeManager._hubserver_pfx(),
198-
EdgeManager.HUB_MOUNT, self.edgeCert.get_pfx_file_path(EC.EDGE_HUB_SERVER))
187+
EdgeManager.HUB_MOUNT, self.edge_cert.get_pfx_file_path(EC.EDGE_HUB_SERVER))
199188
edgedockerclient.copy_file_to_volume(
200189
EdgeManager.CERT_HELPER, self._device_cert(),
201-
EdgeManager.MODULE_MOUNT, self.edgeCert.get_cert_file_path(EC.EDGE_DEVICE_CA))
190+
EdgeManager.MODULE_MOUNT, self.edge_cert.get_cert_file_path(EC.EDGE_DEVICE_CA))
202191

203192
def start(self, modulesDict, routes):
204193
return
@@ -217,7 +206,7 @@ def getOrAddModule(self, name, islocal):
217206

218207
def outputModuleCred(self, name, islocal, output_file):
219208
connstrENV = 'EdgeHubConnectionString={0}'.format(self.getOrAddModule(name, islocal))
220-
deviceCAEnv = 'EdgeModuleCACertificateFile={0}'.format(self.edgeCert.get_cert_file_path(EC.EDGE_DEVICE_CA))
209+
deviceCAEnv = 'EdgeModuleCACertificateFile={0}'.format(self.edge_cert.get_cert_file_path(EC.EDGE_DEVICE_CA))
221210
cred = [connstrENV, deviceCAEnv]
222211

223212
if output_file is not None:
@@ -231,8 +220,8 @@ def outputModuleCred(self, name, islocal, output_file):
231220

232221
def getModule(self, name, islocal):
233222
moduleUri = "https://{0}/devices/{1}/modules/{2}?api-version=2017-11-08-preview".format(
234-
self.hostname, self.deviceId, name)
235-
sas = Utils.get_iot_hub_sas_token(self.deviceUri, self.key, None)
223+
self.hostname, self.device_id, name)
224+
sas = Utils.get_iot_hub_sas_token(self.device_uri, self.access_key, None)
236225
res = requests.get(
237226
moduleUri,
238227
headers={
@@ -246,8 +235,8 @@ def getModule(self, name, islocal):
246235

247236
def addModule(self, name, islocal):
248237
moduleUri = "https://{0}/devices/{1}/modules/{2}?api-version=2017-11-08-preview".format(
249-
self.hostname, self.deviceId, name)
250-
sas = Utils.get_iot_hub_sas_token(self.deviceUri, self.key, None)
238+
self.hostname, self.device_id, name)
239+
sas = Utils.get_iot_hub_sas_token(self.device_uri, self.access_key, None)
251240
res = requests.put(
252241
moduleUri,
253242
headers={
@@ -256,7 +245,7 @@ def addModule(self, name, islocal):
256245
},
257246
data=json.dumps({
258247
'moduleId': name,
259-
'deviceId': self.deviceId
248+
'deviceId': self.device_id
260249
})
261250
)
262251
if res.ok is not True:
@@ -325,10 +314,10 @@ def _start_edge_hub(self, edgedockerclient, edgeHubConnStr, routes):
325314

326315
edgedockerclient.copy_file_to_volume(
327316
EdgeManager.EDGEHUB, EdgeManager._chain_cert(),
328-
EdgeManager.HUB_MOUNT, self.edgeCert.get_cert_file_path(EC.EDGE_CHAIN_CA))
317+
EdgeManager.HUB_MOUNT, self.edge_cert.get_cert_file_path(EC.EDGE_CHAIN_CA))
329318
edgedockerclient.copy_file_to_volume(
330319
EdgeManager.EDGEHUB, EdgeManager._hubserver_pfx(),
331-
EdgeManager.HUB_MOUNT, self.edgeCert.get_pfx_file_path(EC.EDGE_HUB_SERVER))
320+
EdgeManager.HUB_MOUNT, self.edge_cert.get_pfx_file_path(EC.EDGE_HUB_SERVER))
332321
edgedockerclient.start(hubContainer.get('Id'))
333322

334323
@staticmethod

iotedgehubdev/utils.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,48 @@
1+
import errno
12
import os
23
import shutil
3-
import stat
4-
import errno
54
import socket
6-
import sys
5+
import stat
76
import subprocess
8-
from base64 import b64encode, b64decode
7+
import sys
8+
from base64 import b64decode, b64encode
99
from hashlib import sha256
1010
from hmac import HMAC
1111
from time import time
12+
13+
from .constants import EdgeConstants as EC
1214
from .errors import EdgeFileAccessError
15+
1316
if sys.version_info.major >= 3:
1417
from urllib.parse import quote_plus, urlencode
1518
else:
1619
from urllib import quote_plus, urlencode
1720

1821

1922
class Utils(object):
23+
@staticmethod
24+
def parse_device_connection_str(connection_string):
25+
parts = connection_string.split(';')
26+
if len(parts) > 0:
27+
data = dict()
28+
for part in parts:
29+
if "=" in part:
30+
subparts = [s.strip() for s in part.split("=", 1)]
31+
data[subparts[0]] = subparts[1]
32+
33+
if (EC.HOSTNAME_KEY not in data or
34+
EC.DEVICE_ID_KEY not in data or
35+
EC.ACCESS_KEY_KEY not in data):
36+
if "SharedAccessKeyName" in data:
37+
raise KeyError('Please make sure you are using a device connection string'
38+
'instead of an IoT Hub connection string')
39+
else:
40+
raise KeyError('Error parsing connection string')
41+
42+
return data
43+
else:
44+
raise KeyError('Error parsing connection string')
45+
2046
@staticmethod
2147
def get_hostname():
2248
try:

tests/test_connectionstr.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
from iotedgehubdev.constants import EdgeConstants as EC
3+
from iotedgehubdev.utils import Utils
4+
5+
empty_string = ""
6+
valid_connectionstring = "HostName=testhub.azure-devices.net;DeviceId=testdevice;SharedAccessKey=othergibberish="
7+
invalid_connectionstring = "HostName=testhub.azure-devices.net;SharedAccessKey=othergibberish="
8+
9+
10+
def test_empty_connectionstring():
11+
with pytest.raises(KeyError):
12+
connection_str_dict = Utils.parse_device_connection_str(empty_string)
13+
assert not connection_str_dict
14+
15+
16+
def test_valid_connectionstring():
17+
connection_str_dict = Utils.parse_device_connection_str(valid_connectionstring)
18+
assert connection_str_dict[EC.HOSTNAME_KEY] == "testhub.azure-devices.net"
19+
assert connection_str_dict[EC.DEVICE_ID_KEY] == "testdevice"
20+
assert connection_str_dict[EC.ACCESS_KEY_KEY] == "othergibberish="
21+
22+
23+
def test_invalid_connectionstring():
24+
with pytest.raises(KeyError):
25+
connection_str_dict = Utils.parse_device_connection_str(empty_string)
26+
assert not connection_str_dict

0 commit comments

Comments
 (0)