Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
488 lines (455 sloc) 18 KB
AWSTemplateFormatVersion: "2010-09-09"
Description: Create Greengrass resources and group, with supporting AWS services
Parameters:
CoreName:
Description: Green Core name to be created. A "Thing" with be created with _Core appended to the name
Type: String
Default: gg_cfn
Resources:
GreengrassGroup:
Type: AWS::Greengrass::Group
Properties:
Name: !Ref CoreName
RoleArn: !GetAtt GreengrassResourceRole.Arn
InitialVersion:
CoreDefinitionVersionArn: !Ref GreengrassCoreDefinitionVersion
FunctionDefinitionVersionArn: !GetAtt FunctionDefinition.LatestVersionArn
SubscriptionDefinitionVersionArn: !GetAtt SubscriptionDefinition.LatestVersionArn
GreengrassCoreDefinition:
Type: AWS::Greengrass::CoreDefinition
Properties:
Name: !Join ["_", [!Ref CoreName, "Core"]]
GreengrassCoreDefinitionVersion:
Type: AWS::Greengrass::CoreDefinitionVersion
Properties:
CoreDefinitionId: !Ref GreengrassCoreDefinition
Cores:
- Id: !Join ["_", [!Ref CoreName, "Core"]]
ThingArn: !Join
- ":"
- - "arn:aws:iot"
- !Ref AWS::Region
- !Ref AWS::AccountId
- !Join
- "/"
- - "thing"
- !Join ["_", [!Ref CoreName, "Core"]]
CertificateArn: !Join
- ":"
- - "arn:aws:iot"
- !Ref AWS::Region
- !Ref AWS::AccountId
- !Join
- "/"
- - "cert"
- !GetAtt IoTThing.certificateId
SyncShadow: "false"
FunctionDefinition:
Type: 'AWS::Greengrass::FunctionDefinition'
Properties:
Name: FunctionDefinition
InitialVersion:
DefaultConfig:
Execution:
IsolationMode: GreengrassContainer
Functions:
- Id: !Join ["_", [!Ref CoreName, "sample"] ]
FunctionArn: !Ref GGSampleFunctionVersion
FunctionConfiguration:
Pinned: 'true'
Executable: index.py
MemorySize: '65536'
Timeout: '300'
EncodingType: binary
Environment:
Variables:
CORE_NAME: !Ref CoreName
AccessSysfs: 'false'
Execution:
IsolationMode: GreengrassContainer
RunAs:
Uid: '1'
Gid: '10'
SubscriptionDefinition:
Type: 'AWS::Greengrass::SubscriptionDefinition'
Properties:
Name: SubscriptionDefinition
InitialVersion:
Subscriptions:
- Id: Subscription1
Source: 'cloud'
Subject: !Join
- "/"
- - !Ref CoreName
- "in"
Target: !Ref GGSampleFunctionVersion
- Id: Subscription2
Source: !Ref GGSampleFunctionVersion
Subject: !Join
- "/"
- - !Ref CoreName
- "out"
Target: 'cloud'
- Id: Subscription3
Source: !Ref GGSampleFunctionVersion
Subject: !Join
- "/"
- - !Ref CoreName
- "telem"
Target: 'cloud'
GGSampleFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Join ["_", [!Ref CoreName, "sample"] ]
Description: Long running lambda that provides telemetry and pub/sub echo
Handler: index.function_handler
Runtime: python2.7
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 60
Code:
ZipFile: |
import os
from threading import Timer
import greengrasssdk
counter = 0
client = greengrasssdk.client('iot-data')
def telemetry():
'''Publish incrementing value to telemetry topic every 2 seconds'''
global counter
counter += 1
client.publish(
topic='{}/telem'.format(os.environ['CORE_NAME']),
payload='Example telemetry counter, value: {}'.format(counter)
)
Timer(5, telemetry).start()
# Call telemetry() to start telemetry publish
telemetry()
def function_handler(event, context):
'''Echo message on /in topic to /out topic'''
client.publish(
topic='{}/out'.format(os.environ['CORE_NAME']),
payload=event
)
GGSampleFunctionVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName : !GetAtt GGSampleFunction.Arn
IoTThing:
Type: Custom::IoTThing
Properties:
ServiceToken: !GetAtt CreateThingFunction.Arn
ThingName: !Join ["_", [!Ref CoreName, "Core"]]
CreateThingFunction:
Type: AWS::Lambda::Function
Properties:
Description: Create thing, certificate, and policy, return cert and private key
Handler: index.handler
Runtime: python3.6
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 60
Code:
ZipFile: |
import sys
import cfnresponse
import boto3
from botocore.exceptions import ClientError
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
policyDocument = {
'Version': '2012-10-17',
'Statement': [
{
'Effect': 'Allow',
'Action': 'iot:*',
'Resource': '*'
},
{
'Effect': 'Allow',
'Action': 'greengrass:*',
'Resource': '*'
}
]
}
def handler(event, context):
responseData = {}
try:
logger.info('Received event: {}'.format(json.dumps(event)))
result = cfnresponse.FAILED
client = boto3.client('iot')
thingName=event['ResourceProperties']['ThingName']
if event['RequestType'] == 'Create':
thing = client.create_thing(
thingName=thingName
)
response = client.create_keys_and_certificate(
setAsActive=True
)
certId = response['certificateId']
certArn = response['certificateArn']
certPem = response['certificatePem']
privateKey = response['keyPair']['PrivateKey']
client.create_policy(
policyName='{}-full-access'.format(thingName),
policyDocument=json.dumps(policyDocument)
)
response = client.attach_policy(
policyName='{}-full-access'.format(thingName),
target=certArn
)
response = client.attach_thing_principal(
thingName=thingName,
principal=certArn,
)
logger.info('Created thing: %s, cert: %s and policy: %s' %
(thingName, certId, '{}-full-access'.format(thingName)))
result = cfnresponse.SUCCESS
responseData['certificateId'] = certId
responseData['certificatePem'] = certPem
responseData['privateKey'] = privateKey
responseData['iotEndpoint'] = client.describe_endpoint(endpointType='iot:Data-ATS')['endpointAddress']
elif event['RequestType'] == 'Update':
logger.info('Updating thing: %s' % thingName)
result = cfnresponse.SUCCESS
elif event['RequestType'] == 'Delete':
logger.info('Deleting thing: %s and cert/policy' % thingName)
response = client.list_thing_principals(
thingName=thingName
)
for i in response['principals']:
response = client.detach_thing_principal(
thingName=thingName,
principal=i
)
response = client.detach_policy(
policyName='{}-full-access'.format(thingName),
target=i
)
response = client.update_certificate(
certificateId=i.split('/')[-1],
newStatus='INACTIVE'
)
response = client.delete_certificate(
certificateId=i.split('/')[-1],
forceDelete=True
)
response = client.delete_policy(
policyName='{}-full-access'.format(thingName),
)
response = client.delete_thing(
thingName=thingName
)
result = cfnresponse.SUCCESS
except ClientError as e:
logger.error('Error: {}'.format(e))
result = cfnresponse.FAILED
logger.info('Returning response of: {}, with result of: {}'.format(result, responseData))
sys.stdout.flush()
cfnresponse.send(event, context, result, responseData)
GroupDeploymentReset:
# Allows for deletion of Greengrass group if the deployment status is not
# reset manually via the console or API
Type: Custom::GroupDeploymentReset
DependsOn: GreengrassGroup
Properties:
ServiceToken: !GetAtt GroupDeploymentResetFunction.Arn
Region: !Ref "AWS::Region"
ThingName: !Join ["_", [!Ref CoreName, "Core"]]
GroupDeploymentResetFunction:
Type: AWS::Lambda::Function
Properties:
Description: Resets any deployments during stack delete and manages Greengrass service role needs
Handler: index.handler
Runtime: python3.6
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 60
Environment:
Variables:
STACK_NAME: !Ref "AWS::StackName"
Code:
ZipFile: |
import os
import sys
import json
import logging
import cfnresponse
import boto3
from botocore.exceptions import ClientError
logger = logging.getLogger()
logger.setLevel(logging.INFO)
c = boto3.client('greengrass')
iam = boto3.client('iam')
role_name = 'greengrass_cfn_{}_ServiceRole'.format(os.environ['STACK_NAME'])
def find_group(thingName):
response_auth = ''
response = c.list_groups()
for group in response['Groups']:
thingfound = False
group_version = c.get_group_version(
GroupId=group['Id'],
GroupVersionId=group['LatestVersion']
)
core_arn = group_version['Definition'].get('CoreDefinitionVersionArn', '')
if core_arn:
core_id = core_arn[core_arn.index('/cores/')+7:core_arn.index('/versions/')]
core_version_id = core_arn[core_arn.index('/versions/')+10:len(core_arn)]
thingfound = False
response_core_version = c.get_core_definition_version(
CoreDefinitionId=core_id,
CoreDefinitionVersionId=core_version_id
)
if 'Cores' in response_core_version['Definition']:
for thing_arn in response_core_version['Definition']['Cores']:
if thingName == thing_arn['ThingArn'].split('/')[1]:
thingfound = True
break
if(thingfound):
logger.info('found thing: %s, group id is: %s' % (thingName, group['Id']))
response_auth = group['Id']
return(response_auth)
def manage_greengrass_role(cmd):
if cmd == 'CREATE':
r = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument='{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": "greengrass.amazonaws.com"},"Action": "sts:AssumeRole"}]}',
Description='Role for CloudFormation blog post',
)
role_arn = r['Role']['Arn']
iam.attach_role_policy(
RoleName=role_name,
PolicyArn='arn:aws:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy'
)
c.associate_service_role_to_account(RoleArn=role_arn)
logger.info('Created and associated role {}'.format(role_name))
else:
try:
r = iam.get_role(RoleName=role_name)
role_arn = r['Role']['Arn']
c.disassociate_service_role_from_account()
iam.delete_role(RoleName=role_name)
logger.info('Disassociated and deleted role {}'.format(role_name))
except ClientError:
return
def handler(event, context):
responseData = {}
try:
logger.info('Received event: {}'.format(json.dumps(event)))
result = cfnresponse.FAILED
thingName=event['ResourceProperties']['ThingName']
if event['RequestType'] == 'Create':
try:
c.get_service_role_for_account()
result = cfnresponse.SUCCESS
except ClientError as e:
manage_greengrass_role('CREATE')
logger.info('Greengrass service role created')
result = cfnresponse.SUCCESS
elif event['RequestType'] == 'Delete':
group_id = find_group(thingName)
logger.info('Group id to delete: %s' % group_id)
if group_id:
c.reset_deployments(
Force=True,
GroupId=group_id
)
result = cfnresponse.SUCCESS
logger.info('Forced reset of Greengrass deployment')
manage_greengrass_role('DELETE')
else:
logger.error('No group Id for thing: %s found' % thingName)
except ClientError as e:
logger.error('Error: %s' % e)
result = cfnresponse.FAILED
logger.info('Returning response of: %s, with result of: %s' % (result, responseData))
sys.stdout.flush()
cfnresponse.send(event, context, result, responseData)
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- iot:*
Resource: "*"
- Effect: Allow
Action:
- greengrass:*
Resource: "*"
- Effect: Allow
Action:
- ec2:DescribeReservedInstancesOfferings
Resource: "*"
- Effect: Allow
Action:
- iam:CreateRole
- iam:AttachRolePolicy
- iam:GetRole
- iam:DeleteRole
- iam:PassRole
Resource:
!Join [
"",
[
"arn:aws:iam::",
!Ref "AWS::AccountId",
":role/greengrass_cfn_",
!Ref "AWS::StackName",
"_ServiceRole",
],
]
GreengrassResourceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: greengrass.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- iot:*
Resource: "*"
Outputs:
CertificateId:
Description: "IoT Certificate Id"
Value: !GetAtt IoTThing.certificateId
CertificatePem:
Description: "IoT Certificate Pem"
Value: !GetAtt IoTThing.certificatePem
CertificatePrivateKey:
Description: "IoT Certificate Private Key"
Value: !GetAtt IoTThing.privateKey
IoTEndpoint:
Description: "IoT Endpoint"
Value: !GetAtt IoTThing.iotEndpoint
You can’t perform that action at this time.