Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AWS keywords and create test for connecting thinedge to AWS using keys for authentication #3066

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions tests/RobotFramework/libraries/ThinEdgeIO/ThinEdgeIO.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@
# pylint: disable=invalid-name

import logging
import boto3
from botocore.exceptions import ClientError
import json
import tempfile
import paho.mqtt.client as mqtt
import ssl
from typing import Any, Union, List, Dict
import time
from datetime import datetime
import re
import base64
import os
import paramiko
import traceback
import requests
import shutil
import subprocess
from pathlib import Path
Expand Down Expand Up @@ -71,6 +79,10 @@ def __init__(
# Configure retries
retry.configure_retry_on_members(self, "^_assert_")

def get_adapter(self):
"""Return the current adapter type"""
return self.adapter

def should_delete_device_certificate(self) -> bool:
"""Check if the certificate should be deleted or not
"""
Expand Down Expand Up @@ -486,6 +498,168 @@ def mqtt_match_messages(
]
return matching

#
#AWS
#

@keyword("Create Session With Keys")
def create_session_with_keys(self, aws_access_key_id, aws_secret_access_key, region_name=None):
"""
Creates an AWS session using the provided access key, secret key, and optional region.
Returns a success message if the session creation is successful.
"""
try:
self.session = boto3.Session(
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
region_name=region_name
)
# Try to create an IoT client to verify the session
iot_client = self.session.client('iot')
# Perform a simple operation like listing IoT policies to verify the connection
iot_client.list_policies()
return "AWS session creation successful"
except ClientError as e:
raise RuntimeError(f"Failed to create AWS session: {e}")

def get_iot_endpoint(self):
"""
Retrieves the IoT endpoint for the AWS account.
"""
iot_client = self.session.client('iot')
response = iot_client.describe_endpoint(endpointType='iot:Data-ATS')
return response['endpointAddress']

@keyword("Create New Policy")
def create_new_policy(self, policy_name, policy_file_path):
"""
Creates a new IoT policy with the provided name and reads the policy document from a file.
"""
try:
with open(policy_file_path, 'r') as policy_file:
policy_document = json.load(policy_file)

iot_client = self.session.client('iot')
response = iot_client.create_policy(
policyName=policy_name,
policyDocument=json.dumps(policy_document)
)
return response['policyArn']
except ClientError as e:
raise RuntimeError(f"Failed to create policy: {e}")
except FileNotFoundError:
raise RuntimeError(f"Policy file {policy_file_path} not found.")
except json.JSONDecodeError:
raise RuntimeError(f"Error decoding JSON from policy file {policy_file_path}.")

@keyword("Register Device")
def register_device(self, device_id):
"""
Registers a new IoT Thing with the provided device ID.
"""
try:
iot_client = self.session.client('iot')
response = iot_client.create_thing(
thingName=device_id
)
return response['thingArn']
except ClientError as e:
raise RuntimeError(f"Failed to register device: {e}")

@keyword("Check Policy Exists")
def check_policy_exists(self, policy_name):
"""
Checks if the specified IoT policy exists.
"""
try:
iot_client = self.session.client('iot')
response = iot_client.get_policy(policyName=policy_name)
return True if response else False
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
return False
raise RuntimeError(f"Error checking if policy exists: {e}")

@keyword("Check Device Exists")
def check_device_exists(self, device_id):
"""
Checks if the specified IoT device (thing) exists.
"""
try:
iot_client = self.session.client('iot')
response = iot_client.describe_thing(thingName=device_id)
return True if response else False
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
return False
raise RuntimeError(f"Error checking if device exists: {e}")

@keyword("Configure Device")
def configure_device(self, device_id, policy_name):
"""
Configures the device by creating keys and certificates, attaching the policy to the certificate,
and attaching the certificate to the device.
"""
try:
iot_client = self.session.client('iot')

# Create keys and certificate
response = iot_client.create_keys_and_certificate(setAsActive=True)
certificate_arn = response['certificateArn']
certificate_id = response['certificateId']

# Attach policy to certificate
iot_client.attach_policy(
policyName=policy_name,
target=certificate_arn
)

# Attach certificate to the device
iot_client.attach_thing_principal(
thingName=device_id,
principal=certificate_arn
)

return {
'certificate_arn': certificate_arn,
'certificate_id': certificate_id,
'certificate_pem': response['certificatePem'],
'key_pair': response['keyPair']
}
except ClientError as e:
raise RuntimeError(f"Failed to configure device: {e}")

@keyword("Teardown AWS Resources")
def teardown_aws_resources(self, policy_name, device_id):
"""
Deletes the created IoT policy and the registered device (thing) in AWS IoT Core.
"""
try:
iot_client = self.session.client('iot')

# Detach policy from all principals
response = iot_client.list_targets_for_policy(policyName=policy_name)
targets = response.get('targets', [])
for target in targets:
iot_client.detach_policy(policyName=policy_name, target=target)

# Delete the policy
iot_client.delete_policy(policyName=policy_name)

# Detach all principals from the thing
response = iot_client.list_thing_principals(thingName=device_id)
principals = response.get('principals', [])
for principal in principals:
iot_client.detach_thing_principal(thingName=device_id, principal=principal)

# Delete the thing (device)
iot_client.delete_thing(thingName=device_id)

return "Teardown of AWS resources successful"
except ClientError as e:
raise RuntimeError(f"Failed to teardown AWS resources: {e}")


#
# Service Health Status
#
Expand Down
2 changes: 1 addition & 1 deletion tests/RobotFramework/resources/common.resource
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ ${DEVICE_ADAPTER} %{DEVICE_ADAPTER=docker}
&{C8Y_CONFIG} host=%{C8Y_BASEURL= } username=%{C8Y_USER= } password=%{C8Y_PASSWORD= }

# AWS settings
&{AWS_CONFIG} host=%{AWS_URL= }
&{AWS_CONFIG} host=%{AWS_HOST=} access_key=%{AWS_ACCESS_KEY=} secret_key=%{AWS_SECRET_KEY=} region=%{AWS_REGION=} account=%{AWS_ACCOUNT=}
121 changes: 121 additions & 0 deletions tests/RobotFramework/tests/aws/aws_connect_with_keys.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
*** Settings ***
Documentation Verify that thin-edge.io can successfully connect to AWS IoT Core
... Test assumes that AWS credentials are added to the .env file
... AWS_HOST=
... AWS_ACCESS_KEY=
... AWS_SECRET_KEY=
... AWS_REGION=
... AWS_ACCOUNT=

Resource ../../resources/common.resource
Library ThinEdgeIO
Library OperatingSystem

Suite Setup Custom Setup
Test Teardown Custom Teardown

Test Tags theme:aws test:on_demand

*** Variables ***
${POLICY_NAME}= thinedge.io
${CERT_PATH}= /etc/tedge/device-certs/tedge-certificate.pem
${ROOT_CA_PATH}= /etc/tedge/device-certs/tedge-certificate.pem

*** Test Cases ***

Create AWS IoT Policy and Thing
# Verify the certificate creation
${cert_details}= Execute Command sudo tedge cert show
Log ${cert_details}

# Create the AWS IoT policy file in a temporary format
${POLICY_FILE}= Execute Command mktemp --suffix=.json
Log ${POLICY_FILE}
Set Suite Variable ${POLICY_FILE}
Create AWS IoT Policy File ${POLICY_FILE}

# Create an AWS session
Create Session With Keys ${AWS_CONFIG.access_key} ${AWS_CONFIG.secret_key} ${AWS_CONFIG.region}

# Create a new IoT policy
${policy_arn}= Create New Policy ${POLICY_NAME} ${POLICY_FILE}

# Verify that the policy was created
${policy_exists}= Check Policy Exists ${POLICY_NAME}
Should Be True ${policy_exists} Policy ${POLICY_NAME} should exist after creation.

# Register the device (thing) in AWS IoT
${thing_arn}= Register Device ${DEVICE_SN}

# Verify that the device was created
${device_exists}= Check Device Exists ${DEVICE_SN}
Log ${device_exists}
Should Be True ${device_exists} Device ${DEVICE_SN} should exist after creation.

# Configure the device by attaching the policy and the certificate
${cert_data}= Configure Device ${DEVICE_SN} ${POLICY_NAME}

# Connect the device to AWS IoT Core
${log} Execute Command sudo tedge connect aws
Should Contain ${log} tedge-mapper-aws service successfully started and enabled!



*** Keywords ***

Create AWS IoT Policy File
[Arguments] ${POLICY_FILE}
${policy_content}= Catenate
... {
... "Version": "2012-10-17",
... "Statement": [
... {
... "Effect": "Allow",
... "Action": "iot:Connect",
... "Resource": "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:client/\${iot:Connection.Thing.ThingName}"
... },
... {
... "Effect": "Allow",
... "Action": "iot:Subscribe",
... "Resource": [
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topicfilter/thinedge/\${iot:Connection.Thing.ThingName}/cmd/#",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topicfilter/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/#",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topicfilter/thinedge/devices/\${iot:Connection.Thing.ThingName}/test-connection"
... ]
... },
... {
... "Effect": "Allow",
... "Action": "iot:Receive",
... "Resource": [
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/thinedge/\${iot:Connection.Thing.ThingName}/cmd",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/thinedge/\${iot:Connection.Thing.ThingName}/cmd/*",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/*",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/thinedge/devices/\${iot:Connection.Thing.ThingName}/test-connection"
... ]
... },
... {
... "Effect": "Allow",
... "Action": "iot:Publish",
... "Resource": [
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/thinedge/\${iot:Connection.Thing.ThingName}/td",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/thinedge/\${iot:Connection.Thing.ThingName}/td/*",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/*",
... "arn:aws:iot: ${AWS_CONFIG.region}: ${AWS_CONFIG.account}:topic/thinedge/devices/\${iot:Connection.Thing.ThingName}/test-connection"
... ]
... }
... ]
... }
OperatingSystem.Create File ${POLICY_FILE} ${policy_content}

Custom Setup
${DEVICE_SN}= Setup skip_bootstrap=False
Set Suite Variable ${DEVICE_SN}
${log} Execute Command sudo tedge config set aws.url ${AWS_CONFIG.host}

Custom Teardown
Teardown AWS Resources ${POLICY_NAME} ${DEVICE_SN}
Execute Command sudo rm -f ${CERT_PATH} ${ROOT_CA_PATH}
OperatingSystem.Remove File ${POLICY_FILE}
Get Logs
Loading